51

Basically, I am wondering if there is a way to automatically run a function when an element becomes hidden or visible, not on a user click but automatically in another script.

I don't want this to just run one time, because the elements (such as a slider) constantly change from visible to hidden.

Would this be something that jQuery can do with bind? Such as binding the element's visibility to a function (I don't know how to write this)

If you need me to elaborate more on what I'm trying to do, let me know. Thanks

Pseudocode:

$('#element').bind('display:none', function);
function(){
    //do something when element is display:none
}

$('#element').bind('display:block', function2);
function2(){
    //do opposite of function
}
sl133
  • 1,339
  • 2
  • 15
  • 28
  • 1
    If you do the hidden or visiblity by yourself, you can add it after it. – Hadash Dec 10 '13 at 06:21
  • What `event` changes the element's visibility? – Dom Dec 10 '13 at 06:21
  • It's a slider, so there is a script that changes the slides automatically – sl133 Dec 10 '13 at 06:22
  • 1
    possible duplicate of [Jquery -- Action fires on any event](http://stackoverflow.com/questions/5123356/jquery-action-fires-on-any-event) - or at least grab the events the slider listens to as well – mplungjan Dec 10 '13 at 06:22
  • There's no visibility on that one @mplungjan – sl133 Dec 10 '13 at 06:23
  • No, there is any event that might trigger. Here is a better one from just looking to the right of this question [jQuery event to trigger action when a div is made visible](http://stackoverflow.com/questions/1225102/jquery-event-to-trigger-action-when-a-div-is-made-visible?rq=1) – mplungjan Dec 10 '13 at 06:24
  • Dear friends, please have look at `:visible` and `:hidden` selector from jQuery and learn how to implement them for your problem. use `.one()` jQuery event to trigger function for only once... hope this helps – Uday Hiwarale Dec 10 '13 at 06:36
  • .one is not the event handler to use here – mplungjan Dec 10 '13 at 06:48
  • 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:38

6 Answers6

29

There are no events in JQuery to detect css changes.
Refer here: onHide() type event in jQuery

It is possible:

DOM L2 Events module defines mutation events; one of them - DOMAttrModified is the one you need. Granted, these are not widely implemented, but are supported in at least Gecko and Opera browsers.
Source: Event detect when css property changed using Jquery

Without events, you can use setInterval function, like this:

var maxTime = 5000, // 5 seconds
    startTime = Date.now();

var interval = setInterval(function () {
        if ($('#element').is(':visible')) {
            // visible, do something
            clearInterval(interval);
        } else {
            // still hidden
            if (Date.now() - startTime > maxTime) {
                // hidden even after 'maxTime'. stop checking.
                clearInterval(interval);
            }
        }
    },
    100 // 0.1 second (wait time between checks)
);

Note that using setInterval this way, for keeping a watch, may affect your page's performance.

7th July 2018:
Since this answer is getting some visibility and up-votes recently, here is additional update on detecting css changes:

Mutation Events have been now replaced by the more performance friendly Mutation Observer.

The MutationObserver interface provides the ability to watch for changes being made to the DOM tree. It is designed as a replacement for the older Mutation Events feature which was part of the DOM3 Events specification.

Refer: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver

Ganesh Jadhav
  • 2,830
  • 1
  • 20
  • 32
  • 1
    This would work, so I do not see why the down-voter could not comment as to why he voted - for example that it is perhaps not a great thing to monitor things, but if that is all we can do it is a solution – mplungjan Dec 10 '13 at 06:27
  • Ok, it works with the selector :hidden, but I think it only loads once. Can you see my edited OP to see the change? – sl133 Dec 10 '13 at 06:42
  • The `$('#element').not(':visible')` will match `display:none`, so this can work. Did you try it? – Ganesh Jadhav Dec 10 '13 at 06:48
  • Yes I tried it, see my OP. I have no idea why it seems to only run once – sl133 Dec 10 '13 at 06:50
  • I was able to tweak this to work by removing the bool flag visible and just doing the if and else. Thanks – sl133 Dec 10 '13 at 07:09
  • 1
    I think the boolean was to see if you changed from visible to hidden and vice versa – mplungjan Dec 10 '13 at 08:46
  • see this http://stackoverflow.com/questions/1397251/event-detect-when-css-property-changed-using-jquery – tariq Dec 10 '13 at 09:53
19
(function() {
    var ev = new $.Event('display'),
        orig = $.fn.css;
    $.fn.css = function() {
        orig.apply(this, arguments);
        $(this).trigger(ev);
    }
})();

$('#element').bind('display', function(e) {
    alert("display has changed to :" + $(this).attr('style') );
});

$('#element').css("display", "none")// i change the style in this line !!
$('#element').css("display", "block")// i change the style in this line !!

http://fiddle.jshell.net/prollygeek/gM8J2/3/

changes will be alerted.

Marcel Djaman
  • 1,276
  • 1
  • 17
  • 34
ProllyGeek
  • 15,517
  • 9
  • 53
  • 72
  • 1
    Could you explain what it does, I don't really get how it would work with display:block and display:none – sl133 Dec 10 '13 at 06:43
  • you can check the edit , it will alert you when you change the display or Any style to whatever !! – ProllyGeek Dec 10 '13 at 06:47
  • 6
    +1 for avoiding setInterval. This is a really nice solution to a good question. – joews Dec 10 '13 at 07:06
  • 1
    While this might not specifically answer the question, it is intensely cool and clever. :) I've just learned two new things about jQuery. – DanielM May 12 '15 at 13:17
  • @DanielM thank you , i find such comments like yours very encouraging :) . – ProllyGeek May 12 '15 at 17:14
  • This is a cool solution. Unfortunately it doesn't apply to things like media queries, when you hide something at certain screen sizes using CSS. It only fires if you directly hide or show the element (e.g., $('#element').css("display","none")). – Sam Apr 06 '16 at 16:28
  • @SamuelKilada this is not true as jQuery css adds inline style which has higher specifity than class style.. i.e it will override media query, depends on how you declare your media query though. – ProllyGeek Apr 06 '16 at 16:45
  • 1
    What I meant was the alert will not fire when a media query goes into effect which hides or shows that element on the page. I've just tested it myself. :) – Sam Apr 06 '16 at 19:07
  • @Sam are you saying that media query will conflict with this solution ? – ProllyGeek Mar 01 '17 at 12:54
  • @ProllyGeek Yes, I believe that's what I meant when I made that comment. A media query will not cause the event to fire. – Sam Mar 01 '17 at 22:08
4

Tried this on firefox, works http://jsfiddle.net/Tm26Q/1/

$(function(){
 /** Just to mimic a blinking box on the page**/
  setInterval(function(){$("div#box").hide();},2001);
  setInterval(function(){$("div#box").show();},1000);
 /**/
});

$("div#box").on("DOMAttrModified",
function(){if($(this).is(":visible"))console.log("visible");});

UPDATE

Currently the Mutation Events (like DOMAttrModified used in the solution) are replaced by MutationObserver, You can use that to detect DOM node changes like in the above case.

  • @sl133, Hence I mentioned firefox in the answer ^_^, won't work with webkit browsers –  Dec 10 '13 at 07:05
  • I realized that after I implemented it. It works perfectly on firefox, too bad it doesn't work on webkit. – sl133 Dec 10 '13 at 07:05
2

I just Improved ProllyGeek`s answer

Someone may find it useful. you can access displayChanged(event, state) event when .show(), .hide() or .toggle() is called on element

(function() {
  var eventDisplay = new $.Event('displayChanged'),
    origShow = $.fn.show,
    origHide = $.fn.hide;
  //
  $.fn.show = function() {
    origShow.apply(this, arguments);
    $(this).trigger(eventDisplay,['show']);
  };
  //
  $.fn.hide = function() {
    origHide.apply(this, arguments);
    $(this).trigger(eventDisplay,['hide']);
  };
  //
})();

$('#header').on('displayChanged', function(e,state) {
      console.log(state);
});

$('#header').toggle(); // .show() .hide() supported
Community
  • 1
  • 1
django
  • 2,809
  • 5
  • 47
  • 80
1

A catch-all jQuery custom event based on an extension of it's core methods like it was proposed by different people in this thread:

(function() {
    var ev = new $.Event('event.css.jquery'),
        css = $.fn.css,
        show = $.fn.show,
        hide = $.fn.hide;

    // extends css()
    $.fn.css = function() {
        css.apply(this, arguments);
        $(this).trigger(ev);
    };

    // extends show()
    $.fn.show = function() {
        show.apply(this, arguments);
        $(this).trigger(ev);
    };

    // extends hide()
    $.fn.hide = function() {
        hide.apply(this, arguments);
        $(this).trigger(ev);
    };
})();

An external library then, uses sth like $('selector').css('property', value).

As we don't want to alter the library's code but we DO want to extend it's behavior we do sth like:

$('#element').on('event.css.jquery', function(e) {
    // ...more code here...
});

Example: user clicks on a panel that is built by a library. The library shows/hides elements based on user interaction. We want to add a sensor that shows that sth has been hidden/shown because of that interaction and should be called after the library's function.

Another example: jsfiddle.

centurian
  • 1,168
  • 13
  • 25
-1

I like plugin https://github.com/hazzik/livequery It works without timers!

Simple usage

$('.some:visible').livequery( function(){ ... } );

But you need to fix a mistake. Replace line

$jQlq.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');

to

$jQlq.registerPlugin('show', 'append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove', 'html', 'prop', 'removeProp');