Evento jQuery para acionar ação quando uma div é tornada visível


312

Estou usando o jQuery no meu site e gostaria de acionar determinadas ações quando uma determinada div é visível.

É possível anexar algum tipo de manipulador de eventos "isvisible" a divs arbitrários e executar um determinado código quando a div fica visível?

Gostaria de algo como o seguinte pseudocódigo:

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

O código de alerta ("faça alguma coisa") não deve ser acionado até que o contentDiv seja realmente visível.

Obrigado.


Respostas:


190

Você sempre pode adicionar ao método .show () original para não ter que acionar eventos toda vez que mostrar algo ou se precisar trabalhar com código herdado:

Extensão 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]);
    });
  }
});

Exemplo de uso:

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

Isso efetivamente permite que você faça algo antes do Show e do AfterShow enquanto ainda executa o comportamento normal do método .show () original.

Você também pode criar outro método para não precisar substituir o método .show () original.


7
EDIT: existe apenas uma desvantagem com este método: você terá que repetir a mesma "extensão" para todos os métodos que revelam o elemento: show (), slideDown () etc. É necessário algo mais universal para resolver esse problema de uma só vez e tudo, já que é impossível ter um evento "pronto" para delegate () ou live ().
Shahriyar Imanov 24/02

1
Bom, o único problema é que a fadeTofunção não funciona corretamente após a implementação dessa função
Omid

9
Seu código não parece funcionar com o jQuery mais recente (1.7.1 na data deste comentário). Tenho reformulado esta solução ligeiramente ao trabalho com a mais recente jQuery: stackoverflow.com/a/9422207/135968
mkmurray

1
Não é possível obter esse código para trabalhar com a visibilidade div acionada por uma resposta ajax.
JackTheKnife

Novato aqui. Eu não entendo por que obj (e newCallback) pode ser referenciado fora da declaração function (), como pode ser visto em apply (). Fui ensinado que declarar com var torna as variáveis ​​locais, mas remover "var" as torna automaticamente globais.
Yuta73

85

O problema está sendo tratado pelos observadores de mutação do DOM . Eles permitem que você vincule um observador (uma função) a eventos de alteração de conteúdo, texto ou atributos de elementos dom.

Com o lançamento do IE11, todos os principais navegadores suportam esse recurso, consulte http://caniuse.com/mutationobserver

O código de exemplo é o seguinte:

$(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
É uma pena que o IE ainda não o suporte. caniuse.com/mutationobserver -> Para ver os navegadores que o suportam.
ccsakuweb

2
Isso realmente funciona, e eu não preciso oferecer suporte a navegadores herdados, então é perfeito! Eu adicionei uma prova da resposta do JSFiddle aqui: jsfiddle.net/DanAtkinson/26URF
Dan Atkinson

funciona bem no Chrome, mas não funciona em amora webview 10 cascata (se alguém se importa else;))
Guillaume Gendre

1
Isso não parece funcionar se a alteração de visibilidade for causada por uma alteração de atributo em um ancestral daquele que está sendo monitorado.
Michael

4
Isso não parece funcionar no Chrome 51, não sei por quê. Executando o código acima e pressionando o botão, nenhum alerta.
Kris

76

Não há um evento nativo no qual você possa se conectar, mas você pode acionar um evento a partir do seu script depois de tornar a div visível usando o. função de gatilho

por exemplo

//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
Minha limitação aqui é que eu não necessariamente tenho acesso ao código que mostra () é minha div. Portanto, eu não seria capaz de realmente chamar o método trigger ().
frankadelic 04/08/09

11
O JS foi fornecido por uma equipe de desenvolvimento fora da minha organização. Também é uma espécie de "caixa preta", então não queremos modificar esse código, se possível. Pode ser nossa única escolha.
frankadelic 04/08/09

você sempre pode carimbar suas funções js com sua própria implementação. Parece sombrio no entanto!
Redsquare #

11
@redsquare: E se show()for chamado de vários locais além do bloco de código discutido acima?
Robin Maben

1
Neste exemplo, você deve alterar o nome da função para onIsVisibleporque o uso de "isVisible" é um pouco ambíguo no momento.
Brad Johnson

27

Você pode usar o plugin Live Query do jQuery . E escreva o código da seguinte maneira:

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

Então, toda vez que o contentDiv estiver visível, "faça alguma coisa" será alertado!


Bem, droga, isso funciona. Eu tinha pensado sobre isso e o rejeitei como improvável que funcionasse, sem tentar. Deveria ter tentado. :)
neminem

1
Não funcionou para mim. Eu recebo o erro "livequery não é uma função". Tentei com "jquery-1.12.4.min.js" e "jquery-3.1.1.min.js"
Paul Gorbas 6/16/16

2
@Paul: É um plugin
Christian

Isso pode tornar o site consideravelmente mais lento! O plug-in livequery realiza uma pesquisa rápida nos atributos, em vez de usar métodos eficientes modernos, como observadores de mutação DOM. Então, eu prefiro a solução do @hegemon: stackoverflow.com/a/16462443/19163 (e use a pesquisa apenas como substituto das versões antigas do IE) - vog 1 hour ago
vog

20

A solução da redsquare é a resposta certa.

Porém, como uma solução IN-THEORY, você pode escrever uma função que seleciona os elementos classificados por .visibilityCheck( nem todos os elementos visíveis ) e verificar seu visibilityvalor de propriedade; se trueentão faça alguma coisa.

Posteriormente, a função deve ser realizada periodicamente usando a setInterval()função. Você pode parar o cronômetro usando a clearInterval()chamada bem sucedida.

Aqui está um exemplo:

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

window.setInterval(foo, 100);

Você também pode executar algumas melhorias de desempenho, no entanto, a solução é basicamente absurda para ser usada em ação. Assim...


5
não é uma boa forma de usar uma função implícita dentro de setTimeout / setInterval. Use setTimeout (foo, 100)
redsquare

1
Eu tenho que entregar a você, isso é criativo, embora desagradável. E foi rápido e sujo o suficiente para fazer o truque para mim de uma maneira compatível com o IE8. Obrigado!
JD Smith

ou display:none?
Michael

12

O código a seguir (extraído de http://maximeparmentier.com/2012/11/06/bind-show-hide-events-with-jquery/ ) permitirá que você o use $('#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
Isso funcionou perfeitamente para mim, mas é importante notar que a função quebra o encadeamento no show e oculta as funções, o que quebra muitos plugins. Adicione um retorno na frente el.apply(this, arguments)para corrigir isso.
jaimerump

Era isso que eu estava procurando! O retorno precisa ser adicionado como no comentário de @jaimerump
Brainfeeder

9

Se você deseja acionar o evento em todos os elementos (e elementos filho) que são realmente visíveis, por $ .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;
    }
});

E agora seu elemento:

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

Você pode adicionar substituições a funções adicionais do jQuery adicionando-as à matriz na parte superior (como "attr")


9

um gatilho de evento ocultar / mostrar com base na ideia de Glenns: alternar removido porque dispara mostrar / ocultar e não queremos 2fires para um 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
você também deve verificar attr e removeAttr também?
Andrija

5

Eu tive esse mesmo problema e criei um plugin jQuery para resolvê-lo em nosso site.

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

Aqui está como você o usaria com base no seu exemplo:

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

1
A pesquisa não é muito eficiente e desaceleraria significativamente o navegador se isso fosse usado para muitos elementos.
Cerin


4

O que me ajudou aqui é o polyfill de especificação recente ResizeObserver :

const divEl = $('#section60');

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

Observe que é um navegador e um desempenho cruzado (sem pesquisa).


Funcionou bem para detectar cada vez que uma linha da tabela era mostrada / oculta, diferente de outras, também é uma vantagem: não havia um plug-in necessário!
BrettC 6/08/19

3

Eu fiz uma função setinterval simples para conseguir isso. Se o elemento com a classe div1 estiver visível, ele definirá div2 para estar visível. Não conheço um bom método, mas uma solução simples.

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


2

Este suporte facilita e aciona o evento após a animação! [testado no 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

Basta ligar um gatilho ao seletor e colocar o código no evento de gatilho:

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

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

Eu acho que essa é uma solução bastante elegante. Funcionou para mim.
KIKO Software 19/04

1

Há um plug-in jQuery disponível para assistir a alterações nos atributos DOM,

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

O plugin envolve Tudo o que você precisa fazer é ligar o MutationObserver

Você pode usá-lo para assistir à div usando:

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

1

Espero que isso faça o trabalho da maneira mais simples:

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

Mudei o gatilho de evento hide / show do Catalint com base na ideia de Glenns. Meu problema era que eu tenho um aplicativo modular. Eu mudo entre os módulos que mostram e ocultam divs pais. Então, quando oculto um módulo e mostro outro, com o método dele, tenho um atraso visível quando troco entre os módulos. Às vezes, só preciso iluminar esse evento e em algumas crianças especiais. Decidi notificar apenas os filhos com a 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;
    }
});

Então, quando uma criança deseja ouvir o evento "show" ou "hide", tenho que adicionar a classe "displayObserver" e, quando ela não deseja continuar ouvindo, eu removo a 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);
},

Eu desejo ajuda


0

Uma maneira de fazer isso.
Funciona apenas nas alterações de visibilidade que são feitas pela alteração da classe css, mas também pode ser estendido para observar as alterações nos 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

minha solução:

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

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

Esse controle automático de código não requer controle visível ou invisível da consulta

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.