• Skip to main content
  • Skip to secondary navigation
  • Skip to footer

recoveryArea

Content Management for Everyone!

  • About me
  • Blog
  • Contact me
  • AB Testing
  • WordPress
  • Portfolio

Blog

Blog

Yeah, I even have a blog on my website! This is the place where I drop short write-ups with tips and tricks, offer old hardware for sale, maybe even share a song with you, my dear audience and (future) clientele.

Notify Enter Viewport the proper way: Intersection Observer.

4 February 2020 By iPasqualito

In previous articles I have written about how to track elements when they enter or leave the browsers viewport. The reason why you might want to track this is when you are doing a test on an element that is outside the viewport on page initialisation, you don’t want to count this as a visitor immediately but only when the element is scrolled into view.

If the (changed) element is seen by the visitor, you fire an event, not before. In the past I accomplished this by using something called notify enter viewport. This worked just fine but it came with a lot of caveats.

For instance, notify enter viewport only accepts elements that are already loaded in the DOM. Second, a lot of calculations have to be done inside the function before we know an elements position. It’s complicated.

Modern browsers come with an API that can help us accomplish basically the same in a much simpler way, the Intersection Observer.

Here’s a code example of how I use it in my own AB testing framework.

const observeIntersections = function (config) {
    // loop through all entries in config array
    config.forEach(function (options) {

        document.querySelectorAll(options.element).forEach(function (element) {

            const observer = new IntersectionObserver(function (entries) {
                entries.forEach(function (entry) {

                    if (entry.isIntersecting) {

                        sendDimension({
                            event: 'trackEventNI',
                            eventCategory: abtest.testid + ": " + abtest.testName,
                            eventAction: 'Viewport hit for element [' + element + ']',
                            eventLabel: abtest.variation,
                            eventNonInteraction: true
                        });

                    }
                });
            }, {
                root: null, // use document viewport as container
                rootMargin: "0px",
                threshold: 1 // fire callback as each of these thresholds is reached
            });

            observer.observe(element);

        });

    })
};

observeIntersections([{
        element: 'h4', // the element or elements we want to observe
        tag: 'H4 Element',
        threshold: 1,
        root: null,
        rootMargin: "0px"
    }, {
        element: 'h2#Intersection_observer_concepts_and_usage',
        tag: 'H2 title element',
        threshold: 1,
        root: null,
        rootMargin: "0px"
    }, {
        element: 'iframe.live-sample-frame.sample-code-frame',
        tag: 'IFRAME ELEMENT',
        threshold: [0, 0.5, 1],
        root: null,
        rootMargin: "0px"
    }
]);

Filed Under: AB Testing, JavaScript

Notify Enter Viewport

21 June 2018 By iPasqualito

Sometimes we are testing a page element or component that, when the page has loaded, lies outside of the viewport. This has the simple consequence that you should not count this page load as a visitor to your test. Only when the element you are testing scrolls into view, the visitor can be added to your test. To accomplish this, we needed some code that keeps track of the elements position on the page with regard to the page’s dimensions.

This one still relies on jQuery, I’ll upload a JS only version soon.

var notifyEnterViewport = function(o) {
        var raf = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame,
            l = $(window).height(),
            r = $(window),
            t = r.scrollTop(),
            enter = function(e) {
                e.addClass("MM_inside_viewport"), e.hasClass("MM_outside_viewport") && e.removeClass("MM_outside_viewport")

            },
            exit = function(e) {
                e.addClass("MM_outside_viewport"), e.hasClass("MM_inside_viewport") && e.removeClass("MM_inside_viewport")
            },
            loop = function() {
                var e = r.scrollTop();
                t !== e && (t = e, o.length && $.each(o, function(e) {
                    var o = $(this),
                        raf = o.height(),
                        t = o.offset().top,
                        loop = t - r.scrollTop(),
                        n = t - r.scrollTop() + raf;
                    0 <= loop && n <= l && !o.hasClass("MM_inside_viewport") && (console.log("test: element " + (e + 1) + " scrolled into view"), enter(o)), (n < 0 || l < loop) && !o.hasClass("MM_outside_viewport") && (console.log("test: element " + (e + 1) + " scrolled out of view"), exit(o))
                })), raf(loop)
            };
        raf && loop();
    }
    elementsToCheck = $("div#checkThisElement");
notifyEnterViewport(elementsToCheck);
  • UPDATE!


var notifyEnterViewport = function(el) {
    var w = window,
        raf = w.requestAnimationFrame,
        vpHeight = w.innerHeight,
        lastPgYO = w.pageYOffset,
        scroll = function() {
            var height = el.offsetHeight,
                elOffset = offSet(el),
                top = elOffset.top,
                pTop = top - w.pageYOffset,
                pBottom = pTop + height;
            if (pTop >= 0 && pBottom <= vpHeight && !el.classList.contains('mm_enter')) {
                enter(el);
            }
            if ((pBottom < 0 || pTop > vpHeight) && !el.classList.contains('mm_exit')) {
                exit(el);
            }
        },
        offSet = function(el) {
            var rect = el.getBoundingClientRect(),
                pgxo = w.pageXOffset,
                pgyo = w.pageYOffset;
            return {
                top: rect.top + pgyo,
                left: rect.left + pgxo
            };
        },
        enter = function(el) {
            //console.log("enter: ", el);
            el.classList.add('mm_enter');
            el.classList.remove('mm_exit');
        },
        exit = function(el) {
            //console.log("exit: ", el);
            el.classList.add('mm_exit');
            el.classList.remove('mm_enter');
        },
        loop = function() {
            var PgYO = w.pageYOffset;
            if (lastPgYO === PgYO) {
                raf(loop);
                return;
            } else {
                lastPgYO = PgYO;
                scroll();
                raf(loop);
            }
        };
    if (raf) loop();
};

// call:
notifyEnterViewport(document.querySelector('ul#sub_nav > li.active:not(.first)'));

Filed Under: AB Testing, JavaScript

AB Test jQuery Performance Cheat Sheet

14 October 2016 By iPasqualito

cheating

If you write AB tests with jQuery you have to make sure you write your code as optimised as possible. Every millisecond you shave off results in less chance of unwanted repaint or reflow flickers.

Update: Tip number 1: Do NOT use jQuery! Why wait for a library to load, when vanilla JS nowadays does everything jQuery does – but faster.

Use Faster Selectors.

Know which selectors perform the fastest to optimise your code!

// ID selector - very fast (document.getElementById)
$("#id");
// TAG selector - fast (document.getElementsByTagName)
$("p");, $("input");, $("form");
// CLASS selector - performs well in modern browsers (document.getElementsByClassName)
$(".class");
// ATTRIBUTE SELECTOR - slow - needs document.querySelectorAll to perform OK-ish
$("[attribute=value]");
// PSEUDO selector - slowest - needs document.querySelectorAll to perform OK-ish
$(":hidden");

// also, instead of this:
$("#id p");
// do this:
$("#id").find("p"); // --> limit the scope that has to be searched: more than twice as fast!

Use Caching.

Basically every time you use

$('someselector')

you iterate through the dom. If you need an element more than twice, you should store the element reference!

// instead of this:
$('a.contactus').css('padding', '10px');
$('a.contactus').css('margin', '4px');
$('a.contactus').css('display', 'block');
// do this:
var myvar = $('a.contactus');
myvar.css({
padding: '10px',
margin: '4px',
display: 'block'
}); // element stored, CSS passed as object

Use Chaining.

Chained methods will be slightly faster than multiple methods made on a cached selector, and both ways will be much faster than multiple methods made on non-cached selectors.

// instead of this
$("#object").addClass("active");
$("#object").css("color","#f0f");
$("#object").height(300);
// do this
var myvar = $('a.contactus');
myvar.addClass("active").css("color", "#f0f").height(300);

Use Event Delegation.

Event listeners cost memory.

// instead of this: (an event listener for EVERY table cell)
$('table').find('td').on('click',function() {
$(this).toggleClass('active');
});
// do this: (an event listener for only the table that gets fired by its 'td' children)
$('table').on('click','td',function() {
$(this).toggleClass('active');
});

Use Smarter DOM Manipulation.

Every time the DOM is manipulated, the browser has to repaint and reflow content which can be extremely costly.

// instead of this:
const arr = [reallyLongArrayOfImageURLs];
$.each(arr, function(count, item) {
let newImg = '<li><img src="'+item+'"></li>';;
$('#imgList').append(newImg); // aargh a selector in a loop! and we're adding an element here, too!
});
// do this
var arr = [reallyLongArrayOfImageURLs],
tmp = '';
$.each(arr, function(count, item) {
tmp += '<li><img src="'+item+'"></li>'; // no selector and the HTML to add is stored in a variable...
});
$('#imgList').append(tmp); // ..that will be added once when the loop has finished

Filed Under: AB Testing, JavaScript, Performance

  • « Go to Previous Page
  • Go to page 1
  • Go to page 2
  • Go to page 3
  • Go to Next Page »

Footer

Office

recoveryArea
Nijverheidstraat 11-1
7511 JM Enschede

KvK: 65594940

Goals

Let's make a more elegant, easy to use internet, accessible for everyone. Let's move forward to an empathic, secular, sustainable future, enabled through science and technology.

Read my full profile

Get in touch

  • GitHub
  • LinkedIn
  • Twitter

Copyright © 2022 · From recoveryArea with

This website uses cookies to improve your experience. We'll assume you're ok with this, but you can opt-out if you wish. Cookie settingsACCEPT
Privacy & Cookies Policy

Privacy Overview

This website uses cookies to improve your experience while you navigate through the website. Out of these cookies, the cookies that are categorized as necessary are stored on your browser as they are essential for the working of basic functionalities of the website. We also use third-party cookies that help us analyze and understand how you use this website. These cookies will be stored in your browser only with your consent. You also have the option to opt-out of these cookies. But opting out of some of these cookies may have an effect on your browsing experience.
Necessary
Always Enabled
Necessary cookies are absolutely essential for the website to function properly. This category only includes cookies that ensures basic functionalities and security features of the website. These cookies do not store any personal information.
Functional
Functional cookies help to perform certain functionalities like sharing the content of the website on social media platforms, collect feedbacks, and other third-party features.
Performance
Performance cookies are used to understand and analyze the key performance indexes of the website which helps in delivering a better user experience for the visitors.
Analytics
Analytical cookies are used to understand how visitors interact with the website. These cookies help provide information on metrics the number of visitors, bounce rate, traffic source, etc.
Advertisement
Advertisement cookies are used to provide visitors with relevant ads and marketing campaigns. These cookies track visitors across websites and collect information to provide customized ads.
Others
Other uncategorized cookies are those that are being analyzed and have not been classified into a category as yet.
SAVE & ACCEPT