Zdarzenie jQuery wyzwalające akcję, gdy div jest widoczny


312

Korzystam z jQuery na mojej stronie i chciałbym wywoływać pewne działania, gdy określony div jest widoczny.

Czy możliwe jest dołączenie jakiegoś „niewidocznego” modułu obsługi zdarzeń do dowolnych div i uruchomienie określonego kodu, gdy div jest widoczny?

Chciałbym coś w rodzaju następującego pseudokodu:

$(function() {
  $('#contentDiv').isvisible(function() {
    alert("do something");
  });
});

Kod alertu („zrób coś”) nie powinien zostać uruchomiony, dopóki contentDiv nie stanie się rzeczywiście widoczny.

Dzięki.


Odpowiedzi:


190

Zawsze możesz dodać do oryginalnej .show () metody , abyś nie musiał wyzwalać zdarzeń za każdym razem, gdy coś wyświetlasz, lub jeśli potrzebujesz go do pracy ze starszym kodem:

Rozszerzenie Jquery:

jQuery(function($) {

  var _oldShow = $.fn.show;

  $.fn.show = function(speed, oldCallback) {
    return $(this).each(function() {
      var obj         = $(this),
          newCallback = function() {
            if ($.isFunction(oldCallback)) {
              oldCallback.apply(obj);
            }
            obj.trigger('afterShow');
          };

      // you can trigger a before show if you want
      obj.trigger('beforeShow');

      // now use the old function to show the element passing the new callback
      _oldShow.apply(obj, [speed, newCallback]);
    });
  }
});

Przykład użycia:

jQuery(function($) {
  $('#test')
    .bind('beforeShow', function() {
      alert('beforeShow');
    }) 
    .bind('afterShow', function() {
      alert('afterShow');
    })
    .show(1000, function() {
      alert('in show callback');
    })
    .show();
});

To skutecznie pozwala zrobić coś przedShow i AfterShow, jednocześnie wykonując normalne zachowanie oryginalnej metody .show ().

Możesz także utworzyć inną metodę, abyś nie musiał zastępować oryginalnej metody .show ().


7
EDYCJA: Ta metoda ma tylko jeden minus: będziesz musiał powtórzyć to samo „rozszerzenie” dla wszystkich metod, które ujawniają element: show (), slideDown () itp. Aby rozwiązać ten problem raz i raz, potrzeba czegoś bardziej uniwersalnego wszystkie, ponieważ niemożliwe jest posiadanie „gotowego” zdarzenia dla delegate () lub live ().
— Shahriyar Imanov

1
Dobrze, jedynym problemem jest to, że fadeTofunkcja nie działa poprawnie po zaimplementowaniu tej funkcji
— Omid,

9
Twój kod nie działa z najnowszą wersją jQuery (1.7.1 na dzień tego komentarza). Lekko przerobiłem to rozwiązanie do pracy z najnowszą
— wersją

1
Nie można zmusić tego kodu do pracy z widocznością div wywołaną przez odpowiedź ajax.
— JackTheKnife,

Nowicjusz tutaj. Nie rozumiem, dlaczego do obj (i newCallback) można się odwoływać poza deklaracją funkcji (), jak można to zaobserwować w apply (). Nauczono mnie, że zadeklarowanie za pomocą var powoduje, że zmienne są lokalne, ale usunięcie „var” powoduje, że stają się one automatycznie globalne.
— Yuta73

85

Problem jest rozwiązywany przez obserwatorów mutacji DOM . Pozwalają powiązać obserwatora (funkcję) ze zdarzeniami zmieniającej się treści, tekstu lub atrybutów elementów dom.

Wraz z wydaniem IE11 wszystkie główne przeglądarki obsługują tę funkcję, sprawdź http://caniuse.com/mutationobserver

Przykładowy kod jest następujący:

$(function() {
  $('#show').click(function() {
    $('#testdiv').show();
  });

  var observer = new MutationObserver(function(mutations) {
    alert('Attributes changed!');
  });
  var target = document.querySelector('#testdiv');
  observer.observe(target, {
    attributes: true
  });

});
<div id="testdiv" style="display:none;">hidden</div>
<button id="show">Show hidden div</button>

<script type="text/javascript" src="http://code.jquery.com/jquery-1.9.1.min.js"></script>


3
Szkoda, że ​​IE jeszcze go nie obsługuje. caniuse.com/mutationobserver -> Aby zobaczyć przeglądarki, które go obsługują.
— ccsakuweb,

2
To rzeczywiście działa i nie muszę obsługiwać starszych przeglądarek, więc jest idealny! Dodałem tutaj dowód JSFiddle: jsfiddle.net/DanAtkinson/26URF
— Dan Atkinson

działa dobrze w Chrome, ale nie działa w jeżyna 10 kaskadowej WebView (jeśli ktoś jeszcze trosk;))
— Guillaume Gendre

1
Nie wydaje się to działać, jeśli zmiana widoczności jest spowodowana zmianą atrybutu u przodka monitorowanego.
— Michael

4
To nie wydaje się działać w Chrome 51, nie wiem, dlaczego. Uruchomienie powyższego kodu i naciśnięcie przycisku, bez ostrzeżenia.
— Kris,

76

Nie ma żadnego zdarzenia natywnego, do którego można by się przyłączyć, ale można wywołać zdarzenie ze skryptu po tym, jak div będzie widoczny za pomocą. wyzwalaczfunkcja

na przykład

//declare event to run when div is visible
function isVisible(){
   //do something

}

//hookup the event
$('#someDivId').bind('isVisible', isVisible);

//show div and trigger custom event in callback when div is visible
$('#someDivId').show('slow', function(){
    $(this).trigger('isVisible');
});

45
Moim ograniczeniem tutaj jest to, że niekoniecznie mam dostęp do kodu, który pokazuje moją div. Tak więc nie byłbym w stanie wywołać metody trigger ().
— frankadelic

11
JS został dostarczony przez zespół programistów spoza mojej organizacji. To także coś w rodzaju „czarnej skrzynki”, więc nie chcemy modyfikować tego kodu, jeśli to możliwe. Może to być nasz jedyny wybór.
— frankadelic

zawsze możesz stemplować ich funkcje js własną implementacją. Brzmi jednak ponuro!
— redsquare

11
@redsquare: Co jeśli show()wywoływany jest z wielu miejsc innych niż omawiany powyżej blok kodu?
— Robin Maben,

1
W tym przykładzie powinieneś zmienić nazwę funkcji na, onIsVisibleponieważ użycie „isVisible” jest teraz trochę niejednoznaczne.
— Brad Johnson

27

Możesz użyć wtyczki Live Query jQuery . I napisz kod w następujący sposób:

$('#contentDiv:visible').livequery(function() {
    alert("do something");
});

Wtedy za każdym razem, gdy contentDiv będzie widoczny, zostanie wyświetlone ostrzeżenie „zrób coś”!


Cholera, to działa. Pomyślałem o tym i odrzuciłem go jako mało prawdopodobne, bez próby. Powinienem był spróbować. :)
— neminem,

1
Nie działało dla mnie. Pojawia się błąd „zapytanie na żywo nie jest funkcją”. Próbowałem zarówno z „jquery-1.12.4.min.js”, jak i „jquery-3.1.1.min.js”
— Paul Gorbas

2
@Paul: To jest wtyczka
— Christian

Może to znacznie spowolnić twoją stronę! Wtyczka livequery wykonuje szybkie odpytywanie atrybutów, zamiast korzystać z nowoczesnych wydajnych metod, takich jak obserwatorzy mutacji DOM. Wolałbym więc rozwiązanie @hegemon: stackoverflow.com/a/16462443/19163 (i używać odpytywania tylko jako rezerwy dla starszych wersji IE) - vog 1 godzinę temu
— vog

20

Rozwiązanie redsquare jest właściwą odpowiedzią.

Ale jako rozwiązanie W TEORII możesz napisać funkcję, która wybiera elementy sklasyfikowane przez .visibilityCheck( nie wszystkie widoczne elementy ) i sprawdza ich visibilitywartość właściwości; jeśli trueto zrób coś.

Następnie funkcja powinna być wykonywana okresowo za pomocą setInterval()funkcji. Możesz zatrzymać stoper za pomocą clearInterval()po udanym wywołaniu.

Oto przykład:

function foo() {
    $('.visibilityCheck').each(function() {
        if ($(this).is(':visible')){
            // do something
        }
    });
}

window.setInterval(foo, 100);

Możesz również wprowadzić na nim pewne ulepszenia wydajności, jednak rozwiązanie jest w zasadzie absurdalne i można je stosować w akcji. Więc...


5
nieodpowiednia forma do użycia domyślnej funkcji setTimeout / setInterval. Użyj setTimeout (foo, 100)
— redsquare

1
Muszę ci to przekazać, jest to kreatywne, choć paskudne. I było to po prostu wystarczająco szybkie i brudne, aby zrobić dla mnie trik w sposób zgodny z IE8. Dziękuję Ci!
— JD Smith,

czy display:none?
— Michael

12

Poniższy kod (pobrany z http://maximeparmentier.com/2012/11/06/bind-show-hide-events-w--jquery/ ) umożliwi ci korzystanie $('#someDiv').on('show', someFunc);.

(function ($) {
  $.each(['show', 'hide'], function (i, ev) {
    var el = $.fn[ev];
    $.fn[ev] = function () {
      this.trigger(ev);
      return el.apply(this, arguments);
    };
  });
})(jQuery);

8
Działa to idealnie dla mnie, ale ważne jest, aby pamiętać, że funkcja przerywa tworzenie łańcuchów w funkcjach show i hide, co psuje wiele wtyczek. Dodaj zwrot przed, el.apply(this, arguments)aby to naprawić.
— jaimerump,

Właśnie tego szukałem! Zwrot należy dodać jak w komentarzu z @jaimerump
— Brainfeeder

9

Jeśli chcesz wywołać zdarzenie na wszystkich elementach (i elementach potomnych), które są faktycznie widoczne, za pomocą $ .show, Toggle, ToggleClass, addClass lub removeClass:

$.each(["show", "toggle", "toggleClass", "addClass", "removeClass"], function(){
    var _oldFn = $.fn[this];
    $.fn[this] = function(){
        var hidden = this.find(":hidden").add(this.filter(":hidden"));
        var result = _oldFn.apply(this, arguments);
        hidden.filter(":visible").each(function(){
            $(this).triggerHandler("show"); //No bubbling
        });
        return result;
    }
});

A teraz twój element:

$("#myLazyUl").bind("show", function(){
    alert(this);
});

Możesz dodać przesłonięcia do dodatkowych funkcji jQuery, dodając je do tablicy u góry (np. „Attr”)


9

wyzwalacz zdarzenia ukryj / pokaż oparty na idei Glennsa: usunięto przełącznik, ponieważ uruchamia show / hide i nie chcemy 2 pożarów dla jednego zdarzenia

$(function(){
    $.each(["show","hide", "toggleClass", "addClass", "removeClass"], function(){
        var _oldFn = $.fn[this];
        $.fn[this] = function(){
            var hidden = this.find(":hidden").add(this.filter(":hidden"));
            var visible = this.find(":visible").add(this.filter(":visible"));
            var result = _oldFn.apply(this, arguments);
            hidden.filter(":visible").each(function(){
                $(this).triggerHandler("show");
            });
            visible.filter(":hidden").each(function(){
                $(this).triggerHandler("hide");
            });
            return result;
        }
    });
});

2
powinieneś również sprawdzić attr i removeAttr też?
— Andrija,

5

Miałem ten sam problem i stworzyłem wtyczkę jQuery, aby rozwiązać ten problem na naszej stronie.

https://github.com/shaunbowe/jquery.visibilityChanged

Oto, jak użyjesz go na podstawie swojego przykładu:

$('#contentDiv').visibilityChanged(function(element, visible) {
    alert("do something");
});

1
Odpytywanie nie jest bardzo wydajne i znacznie spowolniłoby przeglądarkę, gdyby było używane dla wielu elementów.
— Cerin,


4

Pomogło mi tutaj ostatnie wypełnienie specyfikacji ResizeObserver :

const divEl = $('#section60');

const ro = new ResizeObserver(() => {
    if (divEl.is(':visible')) {
        console.log("it's visible now!");
    }
});
ro.observe(divEl[0]);

Zauważ, że to crossbrowser i performant (bez odpytywania).


Działa dobrze w wykrywaniu za każdym razem, gdy wiersz tabeli jest wyświetlany / ukryty w przeciwieństwie do innych, a także plusem jest to, że nie była wymagana wtyczka!
— BrettC,

3

Wykonałem prostą funkcję setinterval, aby to osiągnąć. Jeśli element z klasą div1 jest widoczny, ustawia div2 jako widoczny. Nie znam dobrej metody, ale proste rozwiązanie.

setInterval(function(){
  if($('.div1').is(':visible')){
    $('.div2').show();
  }
  else {
    $('.div2').hide();
  }      
}, 100);


2

To wsparcie łagodzenia i wyzwalania zdarzenia po zakończeniu animacji! [testowane na jQuery 2.2.4]

(function ($) {
    $.each(['show', 'hide', 'fadeOut', 'fadeIn'], function (i, ev) {
        var el = $.fn[ev];
        $.fn[ev] = function () {
            var result = el.apply(this, arguments);
            var _self=this;
            result.promise().done(function () {
                _self.triggerHandler(ev, [result]);
                //console.log(_self);
            });
            return result;
        };
    });
})(jQuery);

Zainspirowany http://viralpatel.net/blogs/jquery-trigger-custom-event-show-hide-element/


2

Wystarczy powiązać selektor z wyzwalaczem i umieścić kod w zdarzeniu wyzwalacza:

jQuery(function() {
  jQuery("#contentDiv:hidden").show().trigger('show');

  jQuery('#contentDiv').on('show', function() {
    console.log('#contentDiv is now visible');
    // your code here
  });
});

Myślę, że to dość eleganckie rozwiązanie. To zadziałało dla mnie.
— Oprogramowanie KIKO

1

Dostępna jest wtyczka jQuery do oglądania zmian atrybutów DOM,

https://github.com/darcyclarke/jQuery-Watch-Plugin

Wtyczka otacza Wszystko, co musisz zrobić, to powiązać MutationObserver

Następnie możesz go użyć do oglądania div, używając:

$("#selector").watch('css', function() {
    console.log("Visibility: " + this.style.display == 'none'?'hidden':'shown'));
    //or any random events
});

1

Mam nadzieję, że wykona to zadanie w najprostszy sposób:

$("#myID").on('show').trigger('displayShow');

$('#myID').off('displayShow').on('displayShow', function(e) {
    console.log('This event will be triggered when myID will be visible');
});

0

Zmieniłem wyzwalacz zdarzenia ukryj / pokaż z Catalint w oparciu o pomysł Glenns. Mój problem polegał na tym, że mam modułową aplikację. Przełączam się między modułami pokazującymi i ukrywającymi rodziców div. Potem, gdy ukrywam moduł i pokazuję inny, z jego metodą mam wyraźne opóźnienie przy zmianie między modułami. Czasami potrzebuję tylko zapalić to wydarzenie, a także niektóre specjalne dzieci. Postanowiłem więc powiadomić tylko dzieci o klasie „displayObserver”

$.each(["show", "hide", "toggleClass", "addClass", "removeClass"], function () {
    var _oldFn = $.fn[this];
    $.fn[this] = function () {
        var hidden = this.find(".displayObserver:hidden").add(this.filter(":hidden"));
        var visible = this.find(".displayObserver:visible").add(this.filter(":visible"));
        var result = _oldFn.apply(this, arguments);
        hidden.filter(":visible").each(function () {
            $(this).triggerHandler("show");
        }); 
        visible.filter(":hidden").each(function () {
            $(this).triggerHandler("hide");
        });
        return result;
    }
});

Następnie, gdy dziecko chce słuchać zdarzenia „pokaż” lub „ukryj”, muszę mu dodać klasę „displayObserver”, a gdy nie chce go dalej słuchać, usuwam mu klasę

bindDisplayEvent: function () {
   $("#child1").addClass("displayObserver");
   $("#child1").off("show", this.onParentShow);
   $("#child1").on("show", this.onParentShow);
},

bindDisplayEvent: function () {
   $("#child1").removeClass("displayObserver");
   $("#child1").off("show", this.onParentShow);
},

Życzę pomocy


0

Jednym ze sposobów, aby to zrobić.
Działa tylko w przypadku zmian widoczności, które są dokonywane przez zmianę klasy css, ale można go rozszerzyć, aby również obserwował zmiany atrybutów.

var observer = new MutationObserver(function(mutations) {
        var clone = $(mutations[0].target).clone();
        clone.removeClass();
                for(var i = 0; i < mutations.length; i++){
                    clone.addClass(mutations[i].oldValue);
        }
        $(document.body).append(clone);
        var cloneVisibility = $(clone).is(":visible");
        $(clone).remove();
        if (cloneVisibility != $(mutations[0].target).is(":visible")){
            var visibilityChangedEvent = document.createEvent('Event');
            visibilityChangedEvent.initEvent('visibilityChanged', true, true);
            mutations[0].target.dispatchEvent(visibilityChangedEvent);
        }
});

var targets = $('.ui-collapsible-content');
$.each(targets, function(i,target){
        target.addEventListener('visibilityChanged',VisbilityChanedEventHandler});
        target.addEventListener('DOMNodeRemovedFromDocument',VisbilityChanedEventHandler });
        observer.observe(target, { attributes: true, attributeFilter : ['class'], childList: false, attributeOldValue: true });
    });

function VisbilityChanedEventHandler(e){console.log('Kaboom babe'); console.log(e.target); }

0

moje rozwiązanie:

; (function ($) {
$.each([ "toggle", "show", "hide" ], function( i, name ) {
    var cssFn = $.fn[ name ];
    $.fn[ name ] = function( speed, easing, callback ) {
        if(speed == null || typeof speed === "boolean"){
            var ret=cssFn.apply( this, arguments )
            $.fn.triggerVisibleEvent.apply(this,arguments)
            return ret
        }else{
            var that=this
            var new_callback=function(){
                callback.call(this)
                $.fn.triggerVisibleEvent.apply(that,arguments)
            }
            var ret=this.animate( genFx( name, true ), speed, easing, new_callback )
            return ret
        }
    };
});

$.fn.triggerVisibleEvent=function(){
    this.each(function(){
        if($(this).is(':visible')){
            $(this).trigger('visible')
            $(this).find('[data-trigger-visible-event]').triggerVisibleEvent()
        }
    })
}
})(jQuery);

przykładowe użycie:

if(!$info_center.is(':visible')){
    $info_center.attr('data-trigger-visible-event','true').one('visible',processMoreLessButton)
}else{
    processMoreLessButton()
}

function processMoreLessButton(){
//some logic
}

0
$( window ).scroll(function(e,i) {
    win_top = $( window ).scrollTop();
    win_bottom = $( window ).height() + win_top;
    //console.log( win_top,win_bottom );
    $('.onvisible').each(function()
    {
        t = $(this).offset().top;
        b = t + $(this).height();
        if( t > win_top && b < win_bottom )
            alert("do something");
    });
});

-2
<div id="welcometo">Özhan</div>
<input type="button" name="ooo" 
       onclick="JavaScript:
                    if(document.all.welcometo.style.display=='none') {
                        document.all.welcometo.style.display='';
                    } else {
                        document.all.welcometo.style.display='none';
                    }">

Ta kontrola automatyczna kodu nie wymaga zapytania kontroli widocznej lub niewidocznej

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.