Execute callback in javascript when a div change visibility with html5

There is new html5 API that can be used to trigger a callback when element change visibility. That api is called IntersectionObserver it’s supported by Chrome, Edge and Opera, its similar to MutationObserver.

The code look like this:

var element = document.querySelector('selector');
if (window.IntersectionObserver) {
  var observer = new IntersectionObserver(function(entries) {
    if (entries[0].intersectionRatio) {
      console.log('visible');
    } else {
      console.log('hidden');
    }
  }, {
    root: document.body
  });
  observer.observe(element);
}

NOTE: intersectionRatio will not work if the element have position: fixed so if you can use jQuery you can check $(element).is(':visible');.

If you don’t use jQuery you can take :visible code from jQuery which look like this:

elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length;

To remove the observer you need to call:

observer.unobserve(element);

The element will intersect with body only when it’s visible, and the observer is fired when intersection is changed, so it will fire when element will become visible or not visible. You can read about IntersectionObserver on MDN.

If you’re are using jQuery you can wrap this using special event:

if (window.IntersectionObserver) {
  $.event.special.visibility = {
    setup: function() {
      function event(visibility) {
        var e = $.Event("visibility");
        e.visible = visibility;
        return e;
      }
      var element = this;
      var $element = $(this);
      var observer = new IntersectionObserver(function(entries) {
        var e = event($element.is(':visible'));
        ($.event.dispatch || $.event.handle).call(element, e);
      }, {
        root: document.body
      });
      observer.observe(this);
      $.data(this, 'observer', observer);
    },
    teardown: function() {
      var observer = $.data(this, 'observer');
      if (observer) {
        observer.unobserve(this);
        $.removeData(this, 'observer');
      }
    }
  };
}

and call it like this:

$('div').on('visibility', function(e) {
  $('pre').html(e.visible ? 'visible' : 'hidden');
});

Here is Codepen DEMO.

The event will fire when intersection observer is added, if you don’t want that you may want to disable first check.

      var first_time = true;
      var observer = new IntersectionObserver(function(entries) {
        if (!first_time) {
          var e = event(!!entries[0].intersectionRatio);
          ($.event.dispatch || $.event.handle).call(element, e);
        }
        first_time = false;
      }, {
        root: document.body
      });

How to detect if element is added or removed from DOM?

There is new HTML5 API called MutationObserver that allow to add events when DOM is changing including a case when element is added or removed from DOM. Mutation observer support is pretty good.

Here is example to detect if element is added/removed from DOM:

var in_dom = document.body.contains(element);
var observer = new MutationObserver(function(mutations) {
    if (document.body.contains(element)) {
        if (!in_dom) {
            console.log("element inserted");
        }
        in_dom = true;
    } else if (in_dom) {
        in_dom = false;
        console.log("element removed");
    }

});
observer.observe(document.body, {childList: true});

to stop listening to changes you can run:

observer.disconnect();

DEMO

Cross-Domain LocalStorage

As you may know, LocalStorage is domain based. You can’t read or write from localstorage that’s on different domain, even if that’s subdomain. But there is iframe trick that you can use to store data from domain to it’s subdomain.

Basically to have Cross-Domain LocalStorage, you create an iframe that’s hosted on your other domain, then you send PostMessage to that iframe and inside iframe you set that value of localStorage.
Here is the code:

iFrame

window.onmessage = function(e) {
  if (e.origin !== "http://example.com") {
    return;
  }
  var payload = JSON.parse(e.data);
  localStorage.setItem(payload.key, JSON.stringify(payload.data));
};

main file

window.onload = function() {
    var win = document.getElementsByTagName('iframe')[0].contentWindow;
    var obj = {
       name: "Jack"
    };
    win.postMessage(JSON.stringify({key: 'storage', data: obj}), "*");
};

<a href="http://other.example.com/iframe.html">http://other.example.com/iframe.html</a>

If you want to save and load value from LocalStorage, you can PostMessage with method key that will indicate what action it need to take. You can also send message back from iframe to it’s parent with data read from localStorage.

iframe

document.domain = "example.com";
window.onmessage = function(e) {
    if (e.origin !== "http://example.com") {
        return;
    }
    var payload = JSON.parse(e.data);
    switch(payload.method) {
        case 'set':
            localStorage.setItem(payload.key, JSON.stringify(payload.data));
            break;
        case 'get':
            var parent = window.parent;
            var data = localStorage.getItem(payload.key);
            parent.postMessage(data, "*");
            break;
        case 'remove':
            localStorage.removeItem(payload.key);
            break;
    }
};

Your main page on example.com

window.onload = function() {
    var iframe = document.getElementsByTagName('iframe')[0];
    var win;
    // some browser (don't remember which one) throw exception when you try to access
    // contentWindow for the first time, it work when you do that second time
    try {
        win = iframe.contentWindow;
    } catch(e) {
        win = iframe.contentWindow;
    }
    var obj = {
       name: "Jack"
    };
    // save obj in subdomain localStorage
    win.postMessage(JSON.stringify({key: 'storage', method: "set", data: obj}), "*");
    window.onmessage = function(e) {
        if (e.origin != "http://other.example.com") {
            return;
        }
        // this will log "Jack"
        console.log(JSON.parse(e.data).name);
    };
    // load previously saved data
    win.postMessage(JSON.stringify({key: 'storage', method: "get"}), "*");
};

Here is a demo that use my sysend library to send messages to other tab in same browser (the library use localStorage for this). It use iframe proxy to send message from jcubic.pl to terminal.jcubic.pl and vice versa. Iframe is hosted on both domains. (everything is done by the library, you only need to include the file from repo on other.example.com domain and call sysend.proxy('http://other.example.com')).

The only caveat is that in Firefox, it seems that you need to enable CORS on Iframe. If you use apache server you can enable it in .htaccess file with this code:

Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods: "GET"

it will enable CORS for all files, if you want to enable CORS for a single file, this should work:

<Files "iframe.html">
  Header set Access-Control-Allow-Origin "*"
  Header set Access-Control-Allow-Methods: "GET"
</Files>

And that’s it you now can have Cross-Domain LocalStorage.

40 JavaScript libraries you may not know about

Here is a list of useful libraries I had in my bookmarks, libraries that I need to use in some of my future projects.

  • Drawing and animation
  • Language extensions
  • Utilites
    • URI.js – library for handling urls
    • rangy – Text range library
    • color-thief – pick color(s) from image
    • stacktrace.js – get the stacktrace
    • XRegExp – extended regular expressions
    • -prefix-free – CSS3 without prefixes
    • mathjs – An extensive math library for JavaScript and Node.js
    • numeraljs – library for formatting and manipulating numbers
    • String.js – string manipulation library
    • Ocrad.js – Optical Character Recognition in Javascript
    • filer.js – wrapper over HTML5 File API
  • DOM
  • Events
    • jwerty – keyboard events
    • jquery-simulate – simulate keyboard and mouse events
    • Mousetrap – keyboard shortcuts
    • jQuery hashchange – hashchange event
    • jquery-waypoints – Execute a function when you sroll to the element
    • Steady.js – A jank-free module to do logic on the onscroll event without performance regressions in a @media-query like conditions.
    • sysend.js – Send messages between open pages in the browser (this one is actually my own)