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.