Evento jQuery para activar la acción cuando se hace visible un div


312

Estoy usando jQuery en mi sitio y me gustaría activar ciertas acciones cuando un determinado div se hace visible.

¿Es posible adjuntar algún tipo de controlador de eventos "isvisible" a div arbitrarios y ejecutar cierto código cuando el div se hace visible?

Me gustaría algo como el siguiente pseudocódigo:

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

El código de alerta ("hacer algo") no debe activarse hasta que el contentDiv se haga visible.

Gracias.


Respuestas:


190

Siempre puede agregar al método original .show () para no tener que activar eventos cada vez que muestre algo o si necesita que funcione con código heredado:

Extensión 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]);
    });
  }
});

Ejemplo de uso:

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

Esto efectivamente le permite hacer algo beforeShow y afterShow mientras ejecuta el comportamiento normal del método original .show ().

También podría crear otro método para no tener que anular el método original .show ().


77
EDITAR: solo hay un inconveniente con este método: tendrá que repetir la misma "extensión" para todos los métodos que revelan el elemento: show (), slideDown () etc. Se necesita algo más universal para resolver este problema por una vez y todo, ya que es imposible tener un evento "listo" para delegate () o live ().
Shahriyar Imanov

1
Bien, el único problema es que la fadeTofunción no funciona correctamente después de implementar esta función
Omid

99
Su código no parece funcionar con la última versión de jQuery (1.7.1 en la fecha de este comentario). He reelaborado esta solución ligeramente para trabajar con la última jQuery: stackoverflow.com/a/9422207/135968
mkmurray

1
No se puede hacer que ese código funcione con la visibilidad div activada por una respuesta ajax.
JackTheKnife

Novato aquí. No entiendo por qué se puede hacer referencia a obj (y newCallback) fuera de la declaración de function () como se puede ver en apply (). Me enseñaron que declarar con var hace que las variables sean locales, pero eliminar "var" las hace automáticamente globales.
Yuta73

85

El problema está siendo abordado por observadores de mutación DOM . Le permiten vincular a un observador (una función) a eventos de contenido, texto o atributos cambiantes de elementos dom.

Con el lanzamiento de IE11, todos los principales navegadores admiten esta función, consulte http://caniuse.com/mutationobserver

El código de ejemplo es el siguiente:

$(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
Es una pena que IE aún no lo admita. caniuse.com/mutationobserver -> Para ver los navegadores que lo admiten.
ccsakuweb

2
De hecho, esto funciona, y no necesito admitir navegadores heredados, ¡es perfecto! He agregado una prueba JSFiddle de la respuesta aquí: jsfiddle.net/DanAtkinson/26URF
Dan Atkinson

funciona bien en Chrome pero no funciona en Blackberry 10 Cascade Webview (si a alguien más le importa;))
Guillaume Gendre

1
Esto no parece funcionar si el cambio de visibilidad es causado por un cambio de atributo en un antecesor del que se está monitoreando.
Michael

44
Esto no parece funcionar en Chrome 51, no estoy seguro de por qué. Ejecutando el código anterior y presionando el botón, no hay alerta.
Kris

76

No hay un evento nativo al que pueda conectarse para esto, sin embargo, puede activar un evento desde su secuencia de comandos después de que haya hecho visible el div utilizando. función de disparo

p.ej

//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
Mi limitación aquí es que no necesariamente tengo acceso al código que muestra () es mi div. Por lo tanto, no podría llamar al método trigger ().
frankadelic

11
El JS fue proporcionado por un equipo de desarrollo fuera de mi organización. También es algo así como un "recuadro negro", por lo que no queremos modificar ese código si es posible. Sin embargo, puede ser nuestra única opción.
frankadelic

siempre puede estampar sobre sus funciones js con su propia implementación. Suena sombrío sin embargo!
Redsquare

11
@redsquare: ¿Qué sucede si show()se llama desde varios lugares distintos del bloque de código mencionado anteriormente?
Robin Maben

1
Para este ejemplo, debe cambiar el nombre de la función a onIsVisibleporque el uso de "isVisible" es un poco ambiguo en este momento.
Brad Johnson

27

Puede usar el complemento jQuery's Live Query . Y escriba el código de la siguiente manera:

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

Luego, cada vez que el contentDiv esté visible, "¡haz algo" será alertado!


Bueno dang, eso funciona. Lo había pensado y lo rechacé porque era poco probable que funcionara, sin intentarlo. Debería haberlo intentado. :)
neminem

1
No funciono para mi. Me sale el error "livequery no es una función". Intenté con "jquery-1.12.4.min.js" y "jquery-3.1.1.min.js"
Paul Gorbas

2
@Paul: Es un complemento
Christian

¡Esto puede ralentizar su sitio web considerablemente! El complemento livequery realiza un sondeo rápido de los atributos, en lugar de utilizar métodos modernos y eficientes como los observadores de mutación DOM. Así que preferiría la solución de @hegemon: stackoverflow.com/a/16462443/19163 (y usar el sondeo solo como una alternativa para las versiones antiguas de IE) - vog hace 1 hora
vog

20

La solución de Redsquare es la respuesta correcta.

Pero como una solución IN-THEORY puede escribir una función que está seleccionando los elementos clasificados por .visibilityCheck( no todos los elementos visibles ) y verificar su visibilityvalor de propiedad; si trueentonces haz algo.

Posteriormente, la función debe realizarse periódicamente utilizando la setInterval()función. Puede detener el temporizador utilizando la clearInterval()llamada exitosa.

Aquí hay un ejemplo:

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

window.setInterval(foo, 100);

También puede realizar algunas mejoras de rendimiento, sin embargo, la solución es básicamente absurda para ser utilizada en acción. Entonces...


55
no es una buena forma de usar un func implícito dentro de setTimeout / setInterval. Use setTimeout (foo, 100)
redsquare

1
Tengo que dártelo, esto es creativo, aunque desagradable. Y fue lo suficientemente rápido y sucio como para hacer el truco para mí de una manera compatible con IE8. ¡Gracias!
JD Smith

o display:none?
Michael

12

El siguiente código (extraído de http://maximeparmentier.com/2012/11/06/bind-show-hide-events-with-jquery/ ) le permitirá usarlo $('#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
Esto funcionó perfectamente para mí, pero es importante tener en cuenta que la función rompe el encadenamiento en las funciones mostrar y ocultar, lo que rompe muchos complementos. Agregue un retorno en frente de el.apply(this, arguments)para solucionar esto.
jaimerump

¡Esto es lo que estaba buscando! El retorno debe agregarse como en el comentario de @jaimerump
Brainfeeder

9

Si desea activar el evento en todos los elementos (y elementos secundarios) que realmente se hacen visibles, por $ .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;
    }
});

Y ahora tu elemento:

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

Puede agregar anulaciones a funciones jQuery adicionales agregándolas a la matriz en la parte superior (como "attr")


9

un activador de evento de ocultar / mostrar basado en la idea de Glenns: se quitó la palanca porque activa mostrar / ocultar y no queremos 2 incendios para 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
¿también debería verificar attr y removeAttr también?
Andrija

5

Tuve el mismo problema y creé un complemento jQuery para resolverlo en nuestro sitio.

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

Así es como lo usarías según tu ejemplo:

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

1
El sondeo no es muy eficiente y ralentizaría significativamente el navegador si se usara para muchos elementos.
Cerin


4

Lo que me ayudó aquí es el reciente polyfill de ResizeObserver :

const divEl = $('#section60');

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

Tenga en cuenta que es crossbrowser y performant (sin sondeo).


Funcionó bien para detectar cada vez que se mostraba / ocultaba una fila de la tabla a diferencia de otras, también una ventaja es que no se requería el complemento.
BrettC

3

Hice una simple función setinterval para lograr esto. Si el elemento con la clase div1 está visible, establece que div2 esté visible. No conozco un buen método, sino una solución simple.

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


2

¡Este soporte facilita el evento de activación después de la animación! [probado en 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);

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


2

Simplemente vincule un disparador con el selector y coloque el código en el evento disparador:

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

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

Creo que esta es una solución bastante elegante. Funcionó para mi.
KIKO Software

1

Hay un complemento jQuery disponible para ver los cambios en los atributos DOM,

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

El complemento se ajusta Todo lo que necesita hacer es vincular MutationObserver

Luego puede usarlo para ver el div usando:

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

1

Espero que esto haga el trabajo de la manera más 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');
});

0

Cambié el desencadenador de evento de ocultar / mostrar de Catalint basado en la idea de Glenns. Mi problema fue que tengo una aplicación modular. Cambio entre módulos que muestran y ocultan padres divs. Luego, cuando oculto un módulo y muestro otro, con su método tengo un retraso visible cuando cambio entre módulos. Solo necesito a veces iluminar este evento, y en algunos niños especiales. Así que decidí notificar solo a los niños con la clase "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;
    }
});

Luego, cuando un niño quiere escuchar el evento "show" u "hide", debo agregarle la clase "displayObserver", y cuando no quiere continuar escuchándolo, lo elimino de la clase

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

Deseo ayuda


0

Una forma de hacer esto.
Funciona solo en los cambios de visibilidad realizados por el cambio de clase css, pero también se puede ampliar para observar los cambios de atributos.

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

mi solución:

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

ejemplo de uso:

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

Este código de control automático no requiere consulta visible o control invisible

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.