Evento jQuery per attivare l'azione quando viene reso visibile un div


312

Sto usando jQuery nel mio sito e vorrei innescare determinate azioni quando viene reso visibile un determinato div.

È possibile collegare una sorta di gestore di eventi "isvisible" a div arbitrari e avere un certo codice eseguito quando i div vengono resi visibili?

Vorrei qualcosa di simile al seguente pseudocodice:

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

Il codice di avviso ("fai qualcosa") non deve attivarsi fino a quando il contentDiv non viene reso visibile.

Grazie.


Risposte:


190

Puoi sempre aggiungere il metodo originale .show () in modo da non dover attivare eventi ogni volta che mostri qualcosa o se ne hai bisogno per funzionare con il codice legacy:

Estensione 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]);
    });
  }
});

Esempio di utilizzo:

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

Questo ti consente effettivamente di fare qualcosa prima di Show e afterShow mentre esegui ancora il normale comportamento del metodo originale .show ().

È inoltre possibile creare un altro metodo in modo da non dover sovrascrivere il metodo originale .show ().


7
EDIT: c'è solo un aspetto negativo con questo metodo: dovrai ripetere la stessa "estensione" per tutti i metodi che rivelano l'elemento: show (), slideDown () ecc. È necessario qualcosa di più universale per risolvere questo problema per una volta e tutto, poiché è impossibile avere un evento "pronto" per delegate () o live ().
Shahriyar Imanov,

1
Bene, l'unico problema è che la fadeTofunzione non funziona correttamente dopo l'implementazione di questa funzione
Omid

9
Il tuo codice non sembra funzionare con l'ultimo jQuery (1.7.1 alla data di questo commento). Ho leggermente rielaborato questa soluzione per funzionare con l'ultimo jQuery: stackoverflow.com/a/9422207/135968
mkmurray

1
Non riesco a far funzionare quel codice con la visibilità div attivata da una risposta Ajax.
JackTheKnife,

Newbie qui. Non capisco perché obj (e newCallback) possano essere referenziati al di fuori della dichiarazione di function () come si può vedere in apply (). Mi è stato insegnato che dichiarare con var rende le variabili locali, ma rimuovere "var" le rende automaticamente globali.
Yuta73,

85

Il problema viene affrontato dagli osservatori delle mutazioni DOM . Consentono di associare un osservatore (una funzione) a eventi che cambiano contenuto, testo o attributi di elementi dom.

Con il rilascio di IE11, tutti i principali browser supportano questa funzione, consultare http://caniuse.com/mutationobserver

Il codice di esempio è il seguente:

$(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
È un peccato che IE non lo supporti ancora. caniuse.com/mutationobserver -> Per vedere i browser che lo supportano.
ccsakuweb,

2
Funziona davvero, e non ho bisogno di supportare i browser legacy, quindi è perfetto! I'br ha aggiunto una prova JSFiddle della risposta qui: jsfiddle.net/DanAtkinson/26URF
Dan Atkinson,

funziona bene con Chrome ma non funziona con Blackberry 10 Cascade Webview (se a qualcun altro se ne frega;))
Guillaume Gendre

1
Questo non sembra funzionare se la modifica della visibilità è causata da una modifica dell'attributo in un antenato di quella monitorata.
Michael,

4
Questo non sembra funzionare in Chrome 51, non so perché. Eseguendo il codice sopra e premendo il pulsante, nessun avviso.
Kris,

76

Non è possibile agganciare un evento nativo per questo, tuttavia è possibile attivare un evento dallo script dopo aver reso visibile il div utilizzando il. funzione trigger

per esempio

//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
La mia limitazione qui è che non ho necessariamente accesso al codice che mostra il mio div (). Quindi non sarei in grado di chiamare effettivamente il metodo trigger ().
frankadelic,

11
Il JS è stato fornito da un team di sviluppo esterno alla mia organizzazione. È anche una specie di "scatola nera", quindi non vogliamo modificare quel codice se possibile. Potrebbe essere la nostra unica scelta però.
frankadelic,

puoi sempre timbrare le loro funzioni js con la tua implementazione. Sembra triste!
Redsquare

11
@redsquare: cosa succede se show()viene chiamato da più posizioni diverse dal blocco di codice discusso sopra?
Robin Maben,

1
Per questo esempio, dovresti cambiare il nome della funzione in onIsVisibleperché l'uso di "isVisible" è un po 'ambiguo in questo momento.
Brad Johnson,

27

È possibile utilizzare il plug-in Live Query di jQuery . E scrivi il codice come segue:

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

Quindi ogni volta che contentDiv è visibile, "fai qualcosa" verrà avvisato!


Bene dang, funziona. Ci avevo pensato e lo avevo respinto perché non funzionava, senza provarlo. Avrei dovuto provarlo. :)
neminem,

1
Non ha funzionato per me. Viene visualizzato l'errore "livequery non è una funzione". Provato con "jquery-1.12.4.min.js" e "jquery-3.1.1.min.js"
Paul Gorbas il

2
@Paul: è un plugin
Christian il

Questo potrebbe rallentare notevolmente il tuo sito web! Il plug-in livequery esegue un polling rapido sugli attributi, anziché utilizzare metodi moderni ed efficienti come gli osservatori di mutazioni DOM. Quindi preferisco la soluzione di @hegemon: stackoverflow.com/a/16462443/19163 (e l'uso di polling solo come ripiego per le vecchie versioni di IE) - VOG 1 giorno fa
VOG

20

La soluzione di redsquare è la risposta giusta.

Ma come soluzione IN-THEORY puoi scrivere una funzione che sta selezionando gli elementi classificati per .visibilityCheck( non tutti gli elementi visibili ) e controllare il loro visibilityvalore di proprietà; se truepoi fai qualcosa.

Successivamente, la funzione deve essere eseguita periodicamente utilizzando la setInterval()funzione. È possibile arrestare il timer utilizzando la clearInterval()corretta chiamata.

Ecco un esempio:

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

window.setInterval(foo, 100);

Puoi anche eseguire alcuni miglioramenti delle prestazioni su di esso, tuttavia, la soluzione è praticamente assurda da utilizzare in azione. Così...


5
non è una buona forma per usare una funzione implicita all'interno di setTimeout / setInterval. Usa setTimeout (foo, 100)
redsquare il

1
Devo consegnartelo, è creativo, anche se brutto. Ed è stato abbastanza veloce e sporco da farmi il trucco in modo conforme a IE8. Grazie!
JD Smith,

o display:none?
Michael,

12

Il seguente codice (estratto da http://maximeparmentier.com/2012/11/06/bind-show-hide-events-with-jquery/ ) ti consentirà di utilizzare $('#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
Questo ha funzionato perfettamente per me, ma è importante notare che la funzione interrompe il concatenamento nello show e nasconde le funzioni, interrompendo molti plug-in. Aggiungi un ritorno davanti a el.apply(this, arguments)per risolvere questo problema.
Jaimerump,

Questo è quello che stavo cercando! Il ritorno deve essere aggiunto come nel commento di @jaimerump
Brainfeeder,

9

Se vuoi attivare l'evento su tutti gli elementi (e gli elementi figlio) che sono effettivamente resi visibili, da $ .show, toggle, toggleClass, addClass o 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;
    }
});

E ora il tuo elemento:

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

È possibile aggiungere sostituzioni a funzioni jQuery aggiuntive aggiungendole all'array in alto (come "attr")


9

un trigger di evento hide / show basato sull'ideea di Glenn: rimosso toggle perché attiva show / hide e non vogliamo 2fire per un evento

$(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
dovresti anche selezionare attr e removeAttr?
Andrija,

5

Ho avuto lo stesso problema e ho creato un plugin jQuery per risolverlo per il nostro sito.

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

Ecco come lo useresti in base al tuo esempio:

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

1
Il polling non è molto efficiente e rallenterebbe in modo significativo il browser se fosse utilizzato per molti elementi.
Cerin,


4

Ciò che mi ha aiutato qui è il recente polyfill delle specifiche ResizeObserver :

const divEl = $('#section60');

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

Nota che è crossbrowser e performant (nessun polling).


Ha funzionato bene per rilevare ogni volta che una riga della tabella veniva mostrata / nascosta a differenza di altre, inoltre un vantaggio è che non era richiesto alcun plug-in!
BrettC

3

Ho fatto una semplice funzione setinterval per raggiungere questo obiettivo. Se l'elemento con la classe div1 è visibile, imposta div2 come visibile. Non conosco un buon metodo, ma una soluzione semplice.

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


2

Questo supporto facilita e attiva l'evento dopo l'animazione fatta! [testato su 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);

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


2

Basta associare un trigger al selettore e inserire il codice nell'evento trigger:

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

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

Penso che questa sia una soluzione abbastanza elegante. Ha funzionato per me.
Software KIKO

1

È disponibile un plug-in jQuery per guardare il cambiamento negli attributi DOM,

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

Il plugin termina Tutto ciò che devi fare è associare MutationObserver

Puoi quindi usarlo per guardare il div usando:

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

1

Spero che questo farà il lavoro nel modo più semplice:

$("#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

Ho modificato il trigger di evento Nascondi / Mostra da Catalint in base all'idea di Glenns. Il mio problema era che avevo un'applicazione modulare. Passo tra i moduli che mostrano e nascondono i genitori div. Quindi, quando nascondo un modulo e ne faccio vedere un altro, con il suo metodo ho un ritardo visibile quando cambio da un modulo all'altro. Ho solo bisogno di alleggerire questo evento e in alcuni bambini speciali. Così ho deciso di avvisare solo i bambini con la classe "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;
    }
});

Quindi quando un bambino vuole ascoltare l'evento "show" o "hide", devo aggiungerlo alla classe "displayObserver" e quando non vuole continuare ad ascoltarlo, rimuovo la classe

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);
},

Desidero aiuto


0

Un modo per farlo.
Funziona solo sulle modifiche alla visibilità apportate dalla modifica della classe css, ma può essere estesa anche per controllare le modifiche agli attributi.

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

la mia soluzione:

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

esempio di utilizzo:

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';
                    }">

Questo controllo automatico del codice non richiede il controllo visibile o invisibile della query

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.