Événement jQuery pour déclencher une action lorsqu'un div est rendu visible

312

J'utilise jQuery dans mon site et je voudrais déclencher certaines actions lorsqu'un certain div est rendu visible.

Est-il possible d'attacher une sorte de gestionnaire d'événement "isvisible" à des div arbitraires et de faire exécuter un certain code quand le div est rendu visible?

Je voudrais quelque chose comme le pseudocode suivant:

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

Le code d'alerte ("faire quelque chose") ne doit pas se déclencher tant que le contentDiv n'est pas réellement rendu visible.

Merci.

frankadelic
la source
Voir ici: stackoverflow.com/questions/1462138/…
Anderson Green

Réponses:

190

Vous pouvez toujours ajouter à la méthode .show () d'origine afin de ne pas avoir à déclencher d'événements à chaque fois que vous affichez quelque chose ou si vous en avez besoin pour travailler avec du code hérité:

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

Exemple d'utilisation:

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

Cela vous permet effectivement de faire quelque chose avant et après le spectacle tout en exécutant toujours le comportement normal de la méthode .show () d'origine.

Vous pouvez également créer une autre méthode pour ne pas avoir à remplacer la méthode .show () d'origine.

Tres
la source
7
EDIT: Il n'y a qu'un seul inconvénient avec cette méthode: vous devrez répéter la même "extension" pour toutes les méthodes qui révèlent l'élément: show (), slideDown () etc. Quelque chose de plus universel est requis pour résoudre ce problème pour une fois et tout, car il est impossible d'avoir un événement "prêt" pour delegate () ou live ().
Shahriyar Imanov
1
Bon, le seul problème est que la fadeTofonction ne fonctionne pas correctement après avoir implémenté cette fonction
Omid
9
Votre code ne semble pas fonctionner avec la dernière jQuery (1.7.1 à la date de ce commentaire). J'ai légèrement retravaillé cette solution pour travailler avec la dernière jQuery: stackoverflow.com/a/9422207/135968
mkmurray
1
Impossible de faire fonctionner ce code avec la visibilité div déclenchée par une réponse ajax.
JackTheKnife
Débutant ici. Je ne comprends pas pourquoi obj (et newCallback) peut être référencé en dehors de la déclaration function () comme on peut le voir dans apply (). On m'a appris que déclarer avec var rend les variables locales, mais supprimer "var" les rend automatiquement globales.
Yuta73
85

Les observateurs des mutations DOM abordent le problème . Ils vous permettent de lier un observateur (une fonction) à des événements de changement de contenu, de texte ou d'attributs d'éléments dom.

Avec la sortie d'IE11, tous les principaux navigateurs prennent en charge cette fonctionnalité, consultez http://caniuse.com/mutationobserver

L'exemple de code est le suivant:

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

Hégémon
la source
3
Dommage qu'IE ne le supporte pas encore. caniuse.com/mutationobserver -> Pour voir les navigateurs qui le supportent.
ccsakuweb
2
Cela fonctionne en effet, et je n'ai pas besoin de prendre en charge les navigateurs hérités, c'est parfait! I'br a ajouté une preuve de réponse JSFiddle
Dan Atkinson
fonctionne bien en chrome mais ne fonctionne pas en blackview 10 cascade webview (si quelqu'un d'autre s'en soucie;))
Guillaume Gendre
1
Cela ne semble pas fonctionner si le changement de visibilité est causé par un changement d'attribut chez un ancêtre de celui surveillé.
Michael
4
Cela ne semble pas fonctionner dans Chrome 51, je ne sais pas pourquoi. Exécution du code ci-dessus et en appuyant sur le bouton, aucune alerte.
Kris
76

Il n'y a pas d'événement natif auquel vous pouvez vous connecter, mais vous pouvez déclencher un événement à partir de votre script après avoir rendu la div visible à l'aide de. fonction de déclenchement

par exemple

//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');
});
carré rouge
la source
45
Ma limitation ici est que je n'ai pas nécessairement accès au code qui montre () ma div. Je ne pourrais donc pas réellement appeler la méthode trigger ().
frankadelic
11
Le JS a été fourni par une équipe de développement extérieure à mon organisation. C'est aussi une sorte de "boîte noire", donc nous ne voulons pas modifier ce code si possible. C'est peut-être notre seul choix.
frankadelic
vous pouvez toujours masquer leurs fonctions js avec votre propre implémentation. Cela semble sombre cependant!
redsquare
11
@redsquare: que se passe-t-il si show()est appelé à partir de plusieurs endroits autres que le bloc de code décrit ci-dessus?
Robin Maben
1
Pour cet exemple, vous devez remplacer le nom de la fonction par onIsVisiblecar l'utilisation de "isVisible" est un peu ambiguë en ce moment.
Brad Johnson
27

Vous pouvez utiliser le plugin Live Query de jQuery . Et écrivez le code comme suit:

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

Ensuite, chaque fois que le contentDiv est visible, "faire quelque chose" sera alerté!

ax003d
la source
Eh bien dang, ça marche. J'y avais pensé et je l'avais rejeté comme peu susceptible de fonctionner, sans l'essayer. J'aurais dû l'essayer. :)
neminem
1
Ça n'a pas marché pour moi. J'obtiens l'erreur "livequery n'est pas une fonction". Essayé avec "jquery-1.12.4.min.js" et "jquery-3.1.1.min.js"
Paul Gorbas
2
@Paul: C'est un plugin
Christian
Cela peut ralentir considérablement votre site Web! Le plugin livequery effectue une interrogation rapide sur les attributs, plutôt que d'utiliser des méthodes modernes et efficaces telles que les observateurs de mutation DOM. Je préfère donc la solution de @hegemon: stackoverflow.com/a/16462443/19163 (et n'utiliser l'interrogation que comme solution de rechange pour les anciennes versions d'IE) - vog il y a 1 heure
vog
20

La solution de redsquare est la bonne réponse.

Mais en tant que solution IN-THEORY, vous pouvez écrire une fonction qui sélectionne les éléments classés par .visibilityCheck( pas tous les éléments visibles ) et vérifier leur visibilityvaleur de propriété; si truealors faites quelque chose.

Ensuite, la fonction doit être exécutée périodiquement à l'aide de la setInterval()fonction. Vous pouvez arrêter le chronomètre à l'aide de l' clearInterval()appel réussi.

Voici un exemple:

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

window.setInterval(foo, 100);

Vous pouvez également y apporter des améliorations de performances, cependant, la solution est fondamentalement absurde pour être utilisée en action. Alors...

sepehr
la source
5
pas une bonne forme pour utiliser une fonction implicite dans setTimeout / setInterval. Utilisez setTimeout (foo, 100)
redsquare
1
Je dois vous le remettre, c'est créatif, bien que méchant. Et c'était juste assez rapide et sale pour faire l'affaire pour moi d'une manière conforme à IE8. Je vous remercie!
JD Smith
ou display:none?
Michael
12

Le code suivant (extrait de http://maximeparmentier.com/2012/11/06/bind-show-hide-events-with-jquery/ ) vous permettra de l'utiliser $('#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
la source
8
Cela a parfaitement fonctionné pour moi, mais il est important de noter que la fonction interrompt le chaînage des fonctions show et hide, ce qui casse beaucoup de plugins. Ajoutez un retour devant el.apply(this, arguments)pour résoudre ce problème.
jaimerump
C'est ce que je cherchais! Le retour doit être ajouté comme dans le commentaire de @jaimerump
Brainfeeder
9

Si vous souhaitez déclencher l'événement sur tous les éléments (et les éléments enfants) qui sont réellement rendus visibles, par $ .show, toggle, toggleClass, addClass ou 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;
    }
});

Et maintenant votre élément:

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

Vous pouvez ajouter des remplacements à des fonctions jQuery supplémentaires en les ajoutant au tableau en haut (comme "attr")

Glenn Lane
la source
9

un déclencheur d'événement de masquage / affichage basé sur l'idée de Glenn: suppression de la bascule car elle déclenche l'affichage / masquage et nous ne voulons pas de 2 feux pour un événement

$(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
la source
2
devriez-vous également vérifier attr et removeAttr aussi?
Andrija
5

J'ai eu ce même problème et j'ai créé un plugin jQuery pour le résoudre pour notre site.

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

Voici comment vous l'utiliseriez en fonction de votre exemple:

$('#contentDiv').visibilityChanged(function(element, visible) {
    alert("do something");
});
Shaun Bowe
la source
1
L'interrogation n'est pas très efficace et ralentirait considérablement le navigateur s'il était utilisé pour de nombreux éléments.
Cerin
4

Utilisez jQuery Waypoints:

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

Autres exemples sur le site de jQuery Waypoints .

Fedir RYKHTIK
la source
1
Cela ne fonctionne que lorsque l'élément devient visible en raison du défilement et non en raison d'autres changements progmatiques.
AdamJones
@AdamJones Lorsque j'utilise un clavier, cela fonctionne comme prévu. Ex .: imakewebthings.com/jquery-waypoints/shortcuts/infinite-scroll
Fedir RYKHTIK
4

Ce qui m'a aidé ici est le polyfill de spécification ResizeObserver récent :

const divEl = $('#section60');

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

Notez qu'il est croisé et performant (pas d'interrogation).

Artem Vasiliev
la source
A bien fonctionné pour détecter chaque fois qu'une ligne de tableau a été affichée / cachée contrairement aux autres, un avantage supplémentaire est qu'il n'y avait pas de plugin requis!
BrettC
3

J'ai fait une simple fonction setinterval pour y parvenir. Si l'élément de classe div1 est visible, il définit div2 pour qu'il soit visible. Je ne connais pas une bonne méthode, mais une solution simple.

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

Cela prend en charge l'accélération et déclenche l'événement après l'animation! [testé sur 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);

Inspiré par http://viralpatel.net/blogs/jquery-trigger-custom-event-show-hide-element/

MSS
la source
2

Il suffit de lier un déclencheur avec le sélecteur et de mettre le code dans l'événement déclencheur:

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

  jQuery('#contentDiv').on('show', function() {
    console.log('#contentDiv is now visible');
    // your code here
  });
});
Nikhil Gyan
la source
Je pense que c'est une solution assez élégante. Ça a marché pour moi.
KIKO Software
1

Un plugin jQuery est disponible pour observer les changements dans les attributs DOM,

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

Le plugin enveloppe Tout ce dont vous avez besoin est de lier MutationObserver

Vous pouvez ensuite l'utiliser pour regarder la div en utilisant:

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

J'espère que cela fera le travail de la manière la plus simple:

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

J'ai changé le déclencheur d'événement masquer / afficher de Catalint basé sur l'idée de Glenns. Mon problème était que j'avais une application modulaire. Je change entre les modules montrant et cachant les parents divs. Ensuite, lorsque je cache un module et que j'en montre un autre, avec sa méthode, j'ai un délai visible lorsque je change de module. Je n'ai besoin que parfois d'allumer cet événement, et chez certains enfants spéciaux. J'ai donc décidé de ne notifier que les enfants avec 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;
    }
});

Puis quand un enfant veut écouter l'événement "show" ou "hide" je dois lui ajouter la classe "displayObserver", et quand il ne veut pas continuer à l'écouter, je lui retire 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);
},

Je souhaite de l'aide

ccsakuweb
la source
0

Une façon de le faire.
Fonctionne uniquement sur les modifications de visibilité apportées par le changement de classe css, mais peut également être étendu pour surveiller les modifications d'attribut.

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
la source
0

ma solution:

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

exemple d'utilisation:

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

function processMoreLessButton(){
//some logic
}
user2699000
la source
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
la source
-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';
                    }">

Ce code de contrôle automatique ne nécessite pas de requête de contrôle visible ou invisible

alpc
la source