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.

frankadelic
fonte
Veja aqui: stackoverflow.com/questions/1462138/…
Anderson Green

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.

Tres
fonte
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>

hegemon
fonte
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');
});
quadrado vermelho
fonte
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!

ax003d
fonte
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...

sepehr
fonte
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);
JellicleCat
fonte
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")

Glenn Lane
fonte
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;
        }
    });
});
catalisador
fonte
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");
});
Shaun Bowe
fonte
1
A pesquisa não é muito eficiente e desaceleraria significativamente o navegador se isso fosse usado para muitos elementos.
Cerin
4

Use pontos de interesse jQuery:

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

Outros exemplos no site dos jQuery Waypoints .

Fedir RYKHTIK
fonte
1
Isso funciona apenas quando o item se torna visível devido à rolagem e não a outras alterações programáticas.
AdamJones 28/03
@AdamJones Quando uso o teclado, ele funciona conforme o esperado. Ex .: imakewebthings.com/jquery-waypoints/shortcuts/infinite-scroll
Fedir RYKHTIK
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).

Artem Vasiliev
fonte
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);
Um mergulho
fonte
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/

MSS
fonte
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
  });
});
Nikhil Gyan
fonte
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
});
tlogbon
fonte
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');
});
Prashant Anand Verma
fonte
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

ccsakuweb
fonte
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); }
Matas Vaitkevicius
fonte
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
}
user2699000
fonte
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
fonte
-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

alpc
fonte