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.

frankadelic
źródło
Zobacz tutaj: stackoverflow.com/questions/1462138/…
Anderson Green

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 ().

Tres
źródło
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>

hegemon
źródło
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');
});
plac Czerwony
źródło
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ś”!

ax003d
źródło
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...

sepehr
źródło
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);
JellicleCat
źródło
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”)

Glenn Lane
źródło
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;
        }
    });
});
Catalint
źródło
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");
});
Shaun Bowe
źródło
1
Odpytywanie nie jest bardzo wydajne i znacznie spowolniłoby przeglądarkę, gdyby było używane dla wielu elementów.
Cerin,
4

Użyj punktów nawigacyjnych jQuery:

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

Inne przykłady na stronie jQuery Waypoints .

Fedir RYKHTIK
źródło
1
Działa to tylko wtedy, gdy element staje się widoczny z powodu przewijania, a nie z powodu innych zmian w programie.
AdamJones
@AdamJones Gdy używam klawiatury, działa ona zgodnie z oczekiwaniami. Np .: imakewebthings.com/jquery-waypoints/shortcuts/infinite-scroll
Fedir RYKHTIK
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).

Artem Wasiliew
źródło
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);
Adi.P
źródło
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/

MSS
źródło
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
  });
});
Nikhil Gyan
źródło
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
});
tlogbon
źródło
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');
});
Prashant Anand Verma
źródło
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

ccsakuweb
źródło
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); }
Matas Vaitkevicius
źródło
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
}
użytkownik2699000
źródło
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");
    });
});
Saifullah khan
źródło
-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

alpc
źródło