363

I'm using jQuery in my site and I would like to trigger certain actions when a certain div is made visible.

Is it possible to attach some sort of "isvisible" event handler to arbitrary divs and have certain code run when they the div is made visible?

I would like something like the following pseudocode:

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

The alert("do something") code should not fire until the contentDiv is actually made visible.

Thanks.

frankadelic
  • 20,543
  • 37
  • 111
  • 164
  • 1
    See here: http://stackoverflow.com/questions/1462138/js-event-listener-for-when-element-becomes-visible – Anderson Green Apr 03 '13 at 02:22
  • 1
    Does this answer your question? [How to check if element is visible after scrolling?](https://stackoverflow.com/questions/487073/how-to-check-if-element-is-visible-after-scrolling) – Aleksandar Apr 08 '21 at 17:39

23 Answers23

197

You could always add to the original .show() method so you don't have to trigger events every time you show something or if you need it to work with legacy code:

Jquery extension:

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

Usage example:

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

This effectively lets you do something beforeShow and afterShow while still executing the normal behavior of the original .show() method.

You could also create another method so you don't have to override the original .show() method.

Zuul
  • 16,217
  • 6
  • 61
  • 88
Tres
  • 5,604
  • 3
  • 19
  • 17
  • 7
    EDIT: There is only one downside with this method: You will have to repeat the same "extension" for all methods that reveal the element: show(), slideDown() etc. Something more universal is required to solve this problem for once and all, since its impossible to have "ready" event for delegate() or live(). – Gelmir Feb 24 '11 at 17:59
  • 1
    Good, the only problem is that `fadeTo` function does not work properly after implementing this function – Omid Nov 11 '11 at 10:56
  • 10
    Your code does not appear to work with the latest jQuery (1.7.1 at the date of this comment). I have reworked this solution slightly to work with the latest jQuery: http://stackoverflow.com/a/9422207/135968 – mkmurray Feb 23 '12 at 22:15
  • 1
    Can't get that code to work with div visibility triggered by an ajax response. – JackTheKnife Oct 07 '16 at 14:39
  • Newbie here. I don't understand why obj (and newCallback) can be referenced outside of function() declaration as can be seen in apply(). I was taught that declaring with var makes variables local, but removing "var" makes them automatically global. – Yuta73 May 03 '17 at 22:51
  • 1
    This one looks faster than @mkmurray: https://stackoverflow.com/a/30678695/1145224 – Anton Lyhin Sep 13 '17 at 21:20
115

The problem is being addressed by DOM mutation observers. They allow you to bind an observer (a function) to events of changing content, text or attributes of dom elements.

With the release of IE11, all major browsers support this feature, check http://caniuse.com/mutationobserver

The example code is a follows:

$(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="https://code.jquery.com/jquery-1.9.1.min.js"></script>
hegemon
  • 6,614
  • 2
  • 32
  • 30
  • 3
    It's a pity that IE does not support it yet. http://caniuse.com/mutationobserver -> To see the browsers that support it. – ccsakuweb Jul 12 '13 at 08:55
  • 3
    This does indeed work, and I don't need to support legacy browsers so is perfect! I'br added a JSFiddle proof of the answer here: http://jsfiddle.net/DanAtkinson/26URF/ – Dan Atkinson Jul 26 '13 at 15:13
  • works well in chrome but doesn't work in blackberry 10 cascade webview (if anyone else cares ;) ) – Guillaume Gendre Aug 07 '13 at 16:11
  • 3
    This doesn't seem to work if the visibility change is caused by an attribute change in an ancestor of the one being monitored. – Michael May 12 '16 at 23:35
  • 4
    This does not seem to work in Chrome 51, not sure why. Running the above code and pressing button, no alert. – Kris Jul 01 '16 at 13:41
  • Still no-go in Chrome 67, at least for the snippet above. – Dan Jun 04 '18 at 16:24
  • Strange. It's working fine for me in Chrome Version 67.0.3396.99 (Official Build) (64-bit) on macOS latest – Chuck Le Butt Jul 26 '18 at 15:59
  • unfortunately this does not trigger any event if the elements container/parent is shown/hidden. – pstanton Apr 03 '19 at 05:38
  • What code in the snippet specifically relates to the visibility of the element becoming true? - which is the original question. It seems that the answer is generic to any attributes where the question was specific to the visible attribute. – PandaWood Feb 18 '20 at 06:08
  • I got this to work with a little insider information. Like a good bureaucrat, MutationObserver takes its job VERY LITERALLY. If visibility changes due to a `style` or `class` attribute change **in a parent element** the callback will not be called. The trick then is to .observe(the right parent). Or .observe(any parent, `{...subtree: true}`) – Bob Stein Jul 26 '20 at 11:32
82

There is no native event you can hook into for this however you can trigger an event from your script after you have made the div visible using the .trigger function

e.g

//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');
});
redsquare
  • 78,161
  • 20
  • 151
  • 159
  • 51
    My limitation here is that I don't necessarily have access to the code that show()'s my div. So I would not be able to actually call the trigger() method. – frankadelic Aug 04 '09 at 03:49
  • 11
    The JS was provided by a development team outside my organization. It's also something of a "black box", so we don't want to modify that code if possible. It may be our only choice though. – frankadelic Aug 04 '09 at 17:01
  • you can always stamp over their js functions with your own implementation. Sounds grim however! – redsquare Aug 04 '09 at 17:33
  • 12
    @redsquare: What if `show()` is called from multiple places other than the code block discussed above? – Robin Maben Sep 09 '11 at 15:16
  • @conqenator it will have no effect and will not trigger thus custom event. – redsquare Sep 09 '11 at 16:45
  • 1
    For this example, you should change the function name to `onIsVisible` because the use of "isVisible" a little ambiguous right now. – Brad Johnson Feb 15 '15 at 01:33
27

You can use jQuery's Live Query plugin. And write code as follows:

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

Then everytime the contentDiv is visible, "do something" will be alerted!

Chuck Le Butt
  • 47,570
  • 62
  • 203
  • 289
ax003d
  • 3,278
  • 29
  • 26
  • Well dang, that works. I had thought about it and rejected it as unlikely to work, without trying it. Should have tried it. :) – neminem Jul 25 '16 at 15:42
  • 2
    Didn't work for me. I get error "livequery is not a function". Tried with both "jquery-1.12.4.min.js" and "jquery-3.1.1.min.js" – Paul Gorbas Dec 06 '16 at 06:29
  • 2
    @Paul: It is a plugin – Christian Jan 26 '17 at 23:08
  • This may slow down your website considerably! The livequery plugin performs fast polling on the attributes, rather than using modern efficient methods such as DOM mutation observers. So I'd prefer the solution of @hegemon: https://stackoverflow.com/a/16462443/19163 (and use polling only as a fallback for old IE versions) – vog 1 hour ago – vog Jul 13 '17 at 05:09
19

redsquare's solution is the right answer.

But as an IN-THEORY solution you can write a function which is selecting the elements classed by .visibilityCheck (not all visible elements) and check their visibility property value; if true then do something.

Afterward, the function should be performed periodically using the setInterval() function. You can stop the timer using the clearInterval() upon successful call-out.

Here's an example:

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

window.setInterval(foo, 100);

You can also perform some performance improvements on it, however, the solution is basically absurd to be used in action. So...

sepehr
  • 17,110
  • 7
  • 81
  • 119
13

The following code (pulled from http://maximeparmentier.com/2012/11/06/bind-show-hide-events-with-jquery/) will enable you to 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
  • 28,480
  • 24
  • 109
  • 162
  • 8
    This worked perfectly for me, but it's important to note that the function breaks chaining on the show and hide functions, which breaks a lot of plugins. Add a return in front of `el.apply(this, arguments)` to fix this. – jaimerump Dec 16 '13 at 23:55
  • This is what I was looking for! The return needs to be added as in the comment from @jaimerump – Brainfeeder Jan 20 '14 at 15:02
9

If you want to trigger the event on all elements (and child elements) that are actually made visible, by $.show, toggle, toggleClass, addClass, or 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;
    }
});

And now your element:

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

You could add overrides to additional jQuery functions by adding them to the array at the top (like "attr")

Glenn Lane
  • 91
  • 1
  • 1
9

a hide/show event trigger based on Glenns ideea: removed toggle because it fires show/hide and we don't want 2fires for one event

$(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
  • 1,895
  • 17
  • 17
6

I had this same problem and created a jQuery plugin to solve it for our site.

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

Here is how you would use it based on your example:

$('#contentDiv').visibilityChanged(function(element, visible) {
    alert("do something");
});
Shaun Bowe
  • 9,840
  • 11
  • 50
  • 71
  • 1
    Polling is not very efficient, and would significantly slow down the browser if that was used for many elements. – Cerin Nov 18 '16 at 01:08
6

What helped me here is recent ResizeObserver spec polyfill:

const divEl = $('#section60');

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

Note that it's crossbrowser and performant (no polling).

Artem Vasiliev
  • 2,063
  • 1
  • 24
  • 21
  • Worked well for detecting each time a table row was shown/hidden unlike others, also a plus is there was no required plugin! – BrettC Aug 06 '19 at 16:09
6

Just bind a trigger with the selector and put the code into the trigger event:

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

  jQuery('#contentDiv').on('show', function() {
    console.log('#contentDiv is now visible');
    // your code here
  });
});
Nikhil Gyan
  • 682
  • 9
  • 16
3

Use jQuery Waypoints :

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

Other examples on the site of jQuery Waypoints.

Fedir RYKHTIK
  • 9,844
  • 6
  • 58
  • 68
  • 1
    This only works when the item becomes visible due to scrolling and not due to other progmatic changes. – AdamJones Mar 28 '14 at 18:19
  • @AdamJones When I'm using keyboard, it works as expected. Ex.: http://imakewebthings.com/jquery-waypoints/shortcuts/infinite-scroll/ – Fedir RYKHTIK Mar 31 '14 at 09:06
  • Terrible, virtually non-existent, documentation. And there doesn't seem to be any way to detect when the item becomes visible on screen. This library only throws an event, so you'll need another library to do the on-screen visibility detection, which makes this useless. – Cerin Nov 20 '20 at 17:26
3

I did a simple setinterval function to achieve this. If element with class div1 is visible, it sets div2 to be visible. I know not a good method, but a simple fix.

setInterval(function(){
  if($('.div1').is(':visible')){
    $('.div2').show();
  }
  else {
    $('.div2').hide();
  }      
}, 100);
Adi.P
  • 370
  • 1
  • 12
2

You can also try jQuery appear plugin as mentioned in parallel thread https://stackoverflow.com/a/3535028/741782

Community
  • 1
  • 1
gorodezkiy
  • 3,299
  • 2
  • 34
  • 42
2

This support easing and trigger event after animation done! [tested on 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);

Inspired By http://viralpatel.net/blogs/jquery-trigger-custom-event-show-hide-element/

MSS
  • 3,520
  • 24
  • 29
1

There is a jQuery plugin available for watching change in DOM attributes,

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

The plugin wraps All you need do is bind MutationObserver

You can then use it to watch the div using:

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

Hope this will do the job in simplest manner:

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

I changed the hide/show event trigger from Catalint based on Glenns idea. My problem was that I have a modular application. I change between modules showing and hiding divs parents. Then when I hide a module and show another one, with his method I have a visible delay when I change between modules. I only need sometimes to liten this event, and in some special childs. So I decided to notify only the childs with the class "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;
    }
});

Then when a child wants to listen for "show" or "hide" event I have to add him the class "displayObserver", and when It does not want to continue listen it, I remove him the class

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

I wish help

ccsakuweb
  • 789
  • 5
  • 17
0

One way to do this.
Works only on visibility changes that are made by css class change, but can be extended to watch for attribute changes too.

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
  • 58,075
  • 31
  • 238
  • 265
0

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

example usage:

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

function processMoreLessButton(){
//some logic
}
r3wt
  • 4,642
  • 2
  • 33
  • 55
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
  • 686
  • 11
  • 19
0

$(function() {
    $(document).click(function (){
        if ($('#contentDiv').is(':visible')) {
            alert("Visible");
        } else {
            alert("Hidden");
        }
    });
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="contentDiv">Test I'm here</div>

<button onclick="$('#contentDiv').toggle();">Toggle the div</button>
George SEDRA
  • 796
  • 8
  • 11
-3
<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';
                    }">

This code auto control not required query visible or unvisible control

tbraun89
  • 2,246
  • 3
  • 25
  • 44
alpc
  • 598
  • 3
  • 6