Flicker & Flicker Control

"Flicker" explained — why it occurs, and how to prevent it on sites that are optimized for fast first paint.

What is flicker?

"Flicker" is how we refer to the visitor experience of witnessing the very brief appearance of your site's above-the-fold CMS' headline and/or image before it quickly changes to a different headline or image from your active Chartbeat headline/image test. This can happen for users accessing your site with slow connection speeds where our JavaScript does not finish swapping out the default headline or image prior to those elements being visible on the page. It can can happen more often if our headline testing code is not placed near the top of your page's source code after the opening <head> tag (adding our code near the top of your site's <head> places it first in line to be downloaded by client browsers, guaranteeing a faster download completion speed).

Customers using our Image Testing add-on to run image tests are more likely to require a solution to flicker ("flicker control") to prevent flash-of-original-image for their end users because our JavaScript requires that your images have finished loading prior to replacing the image. This does not apply for headline tests where the occurrence of flicker will be much less, and headline flicker is less visible than image flicker when it does occur. For this reason, we have different recommendations for our customers to deal with potential flicker based on whether or not your sites will use Headline Testing only or Headline and Image Testing, both outlined below.

Flicker control for Headline Testing only customers

Option 1: Headline flicker control disabled

Our default implementation detailed on the first page of this guide include initializing the flickerControl variable to false and subsequently calling mab.js with the async attribute in your site's header. This setting disables our "flicker control" code contained within chartbeat_mab.js, which would otherwise automatically hide all elements contained within the <body> tags of your website until our code has completed changing headline text in your HTML (in order to prevent the possibility of your visitors experiencing headline flicker).

We recommend this implementation (flickerControl set to false) because headline flicker is unlikely to occur or be detectable by the naked eye for most websites running only headline tests with Chartbeat as long as mab.js is loaded near the top of your site's <head>. This way, our script does not briefly hide elements of your page ("flicker control") to prevent flicker.

The below example demonstrates a homepage with Chartbeat tracking and Headline Testing enabled, flicker control disabled (line 21).

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <!-- Page title -->
  <title>My Site's Homepage</title>
  <meta name="description" content="My Site's Homepage">

  <!-- Set canonical URL -->
  <link rel="canonical" href="https://mysite.com/article-xyz.html" />
  
  <!-- Load Chartbeat tracker and tester -->
  <script type='text/javascript'>
    (function() {
        /** CONFIGURATION START **/
        var _sf_async_config = window._sf_async_config = (window._sf_async_config || {});
        _sf_async_config.uid = #####; //CHANGE THIS
        _sf_async_config.domain = 'mysite.com'; //CHANGE THIS
        _sf_async_config.flickerControl = false;
        _sf_async_config.useCanonical = true;
        _sf_async_config.useCanonicalDomain = true;
        _sf_async_config.sections = ''; //CHANGE THIS TO YOUR SECTION NAME(s)
        _sf_async_config.authors = ''; //CHANGE THIS TO YOUR AUTHOR NAME(s)
        /** CONFIGURATION END **/
        function loadChartbeat() {
            var e = document.createElement('script');
            var n = document.getElementsByTagName('script')[0];
            e.type = 'text/javascript';
            e.async = true;
            e.src = '//static.chartbeat.com/js/chartbeat.js';
            n.parentNode.insertBefore(e, n);
        }
        loadChartbeat();
    })();
  </script>
  <script async src="//static.chartbeat.com/js/chartbeat_mab.js"></script>

</head>

<body>

</body>
</html>

Option 2: Headline flicker control enabled

Tip: We recommend this implementation for websites that are optimized for very fast first paint, making flicker more likely to occur. We’ve found that for most sites using our default implementation (above), headlines are replaced fast enough that there is no noticeable impact to the visitor experience.

If the flickerControl variable is not set to false when ​chartbeat_mab.js loads, we will look for a style tag on the page with id ​#chartbeat-flicker-control-style and if it doesn't exist, we will immediately add a style tag to the page with that id, with one rule that sets the <body> visibility to hidden. Once we’ve loaded our strategy from the CDN and replaced headlines in the document, we remove any style tag on the page with id ​#chartbeat-flicker-control-style​​.This causes a perceived increase to your page's load time since the <body> is hidden temporarily, which may not be ideal.

For this reason, we recommend adding the below code to your Chartbeat <head> snippet only if you need to implement flicker control for headlines. It allows you to adjust the ​#chartbeat-flicker-control-style style tag to target something other than the entire body — for instance, you could target ​#main_content if you have a content container that contains all headlines that would be tested, or target something like ​h1 or ​.headline to only make the actual headline text itself invisible until the text is updated by chartbeat_mab.js​​. The details will depend on how your HTML and CSS are structured, but fine-tuning ​#chartbeat-flicker-control-style may allow you to avoid flash-of-original-headline while allowing most of the page to render even if headlines haven’t been replaced yet.

If implementing flicker control on your site, the flickerControl configuration variable should instead be set to true, and the following code should be added to your site's <head> snippet prior to the chartbeat_mab.js script tag:

<script type='text/javascript'>
    (function() {

        // Change below from "body" to a CSS selector that selects only your headlines
        var css = 'body { visibility: hidden; }'; 
        
        var head = document.head || document.querySelector('head');
        var style = document.createElement('style');
        style.id = 'chartbeat-flicker-control-style';
        head.appendChild(style);
        style.type = 'text/css';
        if (style.styleSheet) {
            // This is required for IE8 and below.
            style.styleSheet.cssText = css;
        } else {
            style.appendChild(document.createTextNode(css));
        }

        window.setTimeout(function () {
            var hider = document.getElementById('chartbeat-flicker-control-style');
            if (hider) {
              hider.parentNode.removeChild(hider);
            }
        }, 1000);
    })();
  </script>

The setTimeout function in this script script above is just a failsafe; in case ​chartbeat_mab.js doesn’t load for any reason, it will make sure the body does not remain hidden. The expected behavior is that ​chartbeat_mab.js is either already in the visitor’s browser cache or loads from our CDN very quickly, updates any headlines under test, and removes the #chartbeat-flicker-control-style​​ tag much much faster than the 1-second timeout included here (line 24 above).

Here's what that looks like altogether, included directly below our default tracking code snippet and prior to the mab.js script tag in our example homepage source code:

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <!-- Page title -->
  <title>My Site's Homepage</title>
  <meta name="description" content="My Site's Homepage">

  <!-- Set canonical URL -->
  <link rel="canonical" href="https://mysite.com/article-xyz.html" />
  
  <!-- Load Chartbeat tracker and tester -->
  <script type='text/javascript'>
    (function() {
        /** CONFIGURATION START **/
        var _sf_async_config = window._sf_async_config = (window._sf_async_config || {});
        _sf_async_config.uid = #####; //CHANGE THIS
        _sf_async_config.domain = 'mysite.com'; //CHANGE THIS
        _sf_async_config.flickerControl = true;
        _sf_async_config.useCanonical = true;
        _sf_async_config.useCanonicalDomain = true;
        _sf_async_config.sections = ''; //CHANGE THIS TO YOUR SECTION NAME(s)
        _sf_async_config.authors = ''; //CHANGE THIS TO YOUR AUTHOR NAME(s)
        /** CONFIGURATION END **/
        function loadChartbeat() {
            var e = document.createElement('script');
            var n = document.getElementsByTagName('script')[0];
            e.type = 'text/javascript';
            e.async = true;
            e.src = '//static.chartbeat.com/js/chartbeat.js';
            n.parentNode.insertBefore(e, n);
        }
        loadChartbeat();
    })();
  </script>
  <script type='text/javascript'>
    (function() {

        // Change below from "body" to a CSS selector that selects only your headlines
        var css = 'body { visibility: hidden; }'; 
        
        var head = document.head || document.querySelector('head');
        var style = document.createElement('style');
        style.id = 'chartbeat-flicker-control-style';
        head.appendChild(style);
        style.type = 'text/css';
        if (style.styleSheet) {
            // This is required for IE8 and below.
            style.styleSheet.cssText = css;
        } else {
            style.appendChild(document.createTextNode(css));
        }

        window.setTimeout(function () {
            var hider = document.getElementById('chartbeat-flicker-control-style');
            if (hider) {
              hider.parentNode.removeChild(hider);
            }
        }, 1000);
    })();
  </script>
  <script async src="//static.chartbeat.com/js/chartbeat_mab.js"></script>

</head>

<body>

</body>
</html>

Tip: Remember to changebodyin your flicker control code (line 42 above) to a CSS selector that targets only your page headlines in order to avoid temporarily hiding all elements in your site body. Also, feel free to adjust the 1000 millisecond timeout (line 61 above) to allow more time for our JS to finish loading (only relevant for users of your site with slow network connections).

Flicker control for Image and Headline Testing customers

Option 1: Image and headline flicker control enabled

The noticeability of image flicker varies from site to site and can go unnoticed entirely by your visitors if you are testing images below the fold where images have time to be replaced before they are viewed. If your homepage team will be image testing above the fold, we highly recommend that you implement our flicker control solution to avoid this issue and ensure that readers have the most seamless experience possible.

In summary, our flicker control code for sites with image testing will auto-hide images and headlines on page load, and only unhides these elements when (A) mab.js finishes loading or (B) a 1-second default timeout elapses. For end users of your site viewing from devices with standard speed network connections, and with mab.js loading near the top of your page's <head> code, mab.js will successfully replace images and headlines before the 1-second timeout and reveal all hidden elements as soon as possible, eliminating user wait and flicker.

If implementing flicker control on your site, the code below should be added to your site's <head> code as one of the first scripts loaded in your site's <head> tags. It requires two customizations:

  • It allows you to set the ​headlineSelector variable to target specific headline elements used in your homepage markup so we can hide all of your homepage headlines very briefly until the headline text is replaced by chartbeat_mab.js​​ for positions with active tests.

  • Similarly, if your site is not using <article> tags to encapsulate the headline, byline, and image, and instead uses custom tag(s) for this, you must set the articleBlockSelector variable to include all possible story selectors. Otherwise, this can be set to an empty string or commented out.

The details will depend on how your HTML and CSS are structured, but fine-tuning ​these variables allows you to avoid headline and image flicker while allowing most of the page to render while our code replaces elements where there are active tests.

<style>
  .cb-it-hide {
    visibility: hidden;
  }
</style>

<script type="text/javascript">
  !function(){
    // Add customization below
    var headlineSelector = "h3, h2, h1";
    var articleBlockSelector = "article, section.item";
    var timeout = 1000;

    if(!("srcset"in document.createElement("img")))return;const e=window.chartbeatFlicker=window.chartbeatFlicker||{};function t(e,t,c,o){const n=document.createElement("style"),i=function(e,t){return e.split(",").reduce((e,c)=>e+c+` ${t} { visibility: hidden; }\n`,"")}(c,o);n.id=t,e.appendChild(n),n.type="text/css",n.appendChild(document.createTextNode(i))}window._sf_async_config=window._sf_async_config||{},e.timeout=timeout||1e3,e.headlineSelector=headlineSelector||"h3, h2, h1",window._sf_async_config.articleBlockSelector=articleBlockSelector||"article, section";const c=window._sf_async_config.articleBlockSelector;e.timeoutFlag=!1,e.strategyFetched=e.strategyFetched||!1;const o=document.head||document.querySelector("head");t(o,"chartbeat-flicker-control-style-ht",c,e.headlineSelector),t(o,"chartbeat-flicker-control-style-it",c,"img"),document.addEventListener("DOMContentLoaded",()=>{if(e.timeoutFlag){const e=document.getElementById("chartbeat-flicker-control-style-it");return void(e&&e.parentNode.removeChild(e))}document.querySelectorAll(c).forEach(function(e){Array.prototype.slice.call(e.querySelectorAll("img")).forEach(function(e){e.getAttribute("pinger-seen")||e.classList.add("cb-it-hide")})});const t=document.getElementById("chartbeat-flicker-control-style-it");t&&t.parentNode.removeChild(t)}),window.setTimeout(function(){if(!e.strategyFetched){e.timeoutFlag=!0;const t=document.getElementById("chartbeat-flicker-control-style-ht");t&&t.parentNode.removeChild(t)}document.querySelectorAll(c).forEach(function(e){Array.prototype.slice.call(e.querySelectorAll("img")).forEach(function(e){"true"!==e.getAttribute("keep-hiding")&&e.classList.remove("cb-it-hide")})});const t=document.getElementById("chartbeat-flicker-control-style-it");t&&t.parentNode.removeChild(t)},e.timeout)
  }();
</script>

Tip: Remember to changeheadlineSelectorand articleBlockSelectorin the code above (lines 10 and 11) to CSS selectors that target only your page headlines and article block tags. Also, feel free to adjust the 1000 millisecond timeout (line 12) to allow more time for our JS to finish loading (only relevant for users of your site with slow network connections).

Here's what this looks like altogether, included as the first script after the opening <head> tag, before the Chartbeat tracking snippet and call to load chartbeat_mab.js (which should only be called after the page title and canonical URL elements):

<!doctype html>

<html lang="en">
<head>
  <style>
    .cb-it-hide {
		  visibility: hidden;
	  }
  </style>

  <script type="text/javascript">
	  !function () {
	  // Add customization below
    var headlineSelector = "h3, h2, h1";
    var articleBlockSelector = "article, section.item";
    var timeout = 1000;

    if (!("srcset" in document.createElement("img"))) return; const e = window.chartbeatFlicker = window.chartbeatFlicker || {}; function t(e, t, c, o) { const n = document.createElement("style"), i = function (e, t) { return e.split(",").reduce((e, c) => e + c + ` ${t} { visibility: hidden; }\n`, "") }(c, o); n.id = t, e.appendChild(n), n.type = "text/css", n.appendChild(document.createTextNode(i)) } window._sf_async_config = window._sf_async_config || {}, e.timeout = timeout, e.headlineSelector = headlineSelector, window._sf_async_config.articleBlockSelector = articleBlockSelector; const c = window._sf_async_config.articleBlockSelector; e.timeoutFlag = !1, e.strategyFetched = e.strategyFetched || !1; const o = document.head || document.querySelector("head"); t(o, "chartbeat-flicker-control-style-ht", c, e.headlineSelector), t(o, "chartbeat-flicker-control-style-it", c, "img"), document.addEventListener("DOMContentLoaded", () => { if (e.timeoutFlag) { const e = document.getElementById("chartbeat-flicker-control-style-it"); return void (e && e.parentNode.removeChild(e)) } const t = document.querySelectorAll(c); let o = []; t.forEach(function (e) { const t = Array.prototype.slice.call(e.querySelectorAll("img")); t.forEach(function (e) { e.getAttribute("pinger-seen") || e.classList.add("cb-it-hide") }), o = o.concat(t) }); const n = document.getElementById("chartbeat-flicker-control-style-it"); n && n.parentNode.removeChild(n) }), window.setTimeout(function () { if (!e.strategyFetched) { e.timeoutFlag = !0; const t = document.getElementById("chartbeat-flicker-control-style-ht"); t && t.parentNode.removeChild(t) } document.querySelectorAll(c).forEach(function (e) { Array.prototype.slice.call(e.querySelectorAll("img")).forEach(function (e) { "true" !== e.getAttribute("keep-hiding") && e.classList.remove("cb-it-hide") }) }) }, e.timeout)
  }();
  </script>
  <meta charset="utf-8">

  <!-- Page title -->
  <title>My Site's Homepage</title>
  <meta name="description" content="My Site's Homepage">

  <!-- Set canonical URL -->
  <link rel="canonical" href="https://mysite.com/article-xyz.html" />
  
  <!-- Load Chartbeat tracker and tester -->
  <script type='text/javascript'>
    (function() {
        /** CONFIGURATION START **/
        var _sf_async_config = window._sf_async_config = (window._sf_async_config || {});
        _sf_async_config.uid = #####; //CHANGE THIS
        _sf_async_config.domain = 'mysite.com'; //CHANGE THIS
        _sf_async_config.flickerControl = false;
        _sf_async_config.useCanonical = true;
        _sf_async_config.useCanonicalDomain = true;
        _sf_async_config.sections = ''; //CHANGE THIS TO YOUR SECTION NAME(s)
        _sf_async_config.authors = ''; //CHANGE THIS TO YOUR AUTHOR NAME(s)
        /** CONFIGURATION END **/
        function loadChartbeat() {
            var e = document.createElement('script');
            var n = document.getElementsByTagName('script')[0];
            e.type = 'text/javascript';
            e.async = true;
            e.src = '//static.chartbeat.com/js/chartbeat.js';
            n.parentNode.insertBefore(e, n);
        }
        loadChartbeat();
    })();
  </script>
  <script async src="//static.chartbeat.com/js/chartbeat_mab.js"></script>

</head>

<body>

</body>
</html>

Note: The headline flicker control configuration variable, _sf_async_config.flickerControl, should be set to false when using this implementation. The Image Testing flicker control code snippet handles all of the flicker control in the above code example.

Option 2: Image and headline flicker control disabled

To implement image and headline testing without flicker control, simply use the default code snippet detailed in the first page of this guide with the flickerControl variable set to false. Here's what this looks like in our example homepage source code:

<!doctype html>

<html lang="en">
<head>
  <meta charset="utf-8">

  <!-- Page title -->
  <title>My Site's Homepage</title>
  <meta name="description" content="My Site's Homepage">

  <!-- Set canonical URL -->
  <link rel="canonical" href="https://mysite.com/article-xyz.html" />
  
  <!-- Load Chartbeat tracker and tester -->
  <script type='text/javascript'>
    (function() {
        /** CONFIGURATION START **/
        var _sf_async_config = window._sf_async_config = (window._sf_async_config || {});
        _sf_async_config.uid = #####; //CHANGE THIS
        _sf_async_config.domain = 'mysite.com'; //CHANGE THIS
        _sf_async_config.flickerControl = false;
        _sf_async_config.articleBlockSelector = 'article'; //CHANGE THIS IF NEEDED
        _sf_async_config.useCanonical = true;
        _sf_async_config.useCanonicalDomain = true;
        _sf_async_config.sections = ''; //CHANGE THIS TO YOUR SECTION NAME(s)
        _sf_async_config.authors = ''; //CHANGE THIS TO YOUR AUTHOR NAME(s)
        /** CONFIGURATION END **/
        function loadChartbeat() {
            var e = document.createElement('script');
            var n = document.getElementsByTagName('script')[0];
            e.type = 'text/javascript';
            e.async = true;
            e.src = '//static.chartbeat.com/js/chartbeat.js';
            n.parentNode.insertBefore(e, n);
        }
        loadChartbeat();
    })();
  </script>
  <script async src="//static.chartbeat.com/js/chartbeat_mab.js"></script>

</head>

<body>

</body>
</html>

Next steps

The next article in this guide details resources requested by mab.js and the (very minimal) effect this script has on page load time if implemented as our documentation suggests with the async attribute in your site's header.

For those ready to implement our code as detailed in the first page of this guide, feel free to skip ahead to our integration QA instructions article to review your work.

Last updated