From 6f96cb8cd2d7907fa38b4940a052db1d4d264b4d Mon Sep 17 00:00:00 2001 From: Stephanie Hobson Date: Wed, 30 Oct 2024 06:34:19 -0700 Subject: [PATCH] Send scroll event to data layer #15098 (#15179) --- .../js/base/datalayer-trackscroll-init.es6.js | 16 ++++ media/js/base/datalayer-trackscroll.es6.js | 85 +++++++++++++++++++ media/static-bundles.json | 3 +- tests/unit/spec/base/datalayer-trackscroll.js | 71 ++++++++++++++++ 4 files changed, 174 insertions(+), 1 deletion(-) create mode 100644 media/js/base/datalayer-trackscroll-init.es6.js create mode 100644 media/js/base/datalayer-trackscroll.es6.js create mode 100644 tests/unit/spec/base/datalayer-trackscroll.js diff --git a/media/js/base/datalayer-trackscroll-init.es6.js b/media/js/base/datalayer-trackscroll-init.es6.js new file mode 100644 index 0000000000..460061e129 --- /dev/null +++ b/media/js/base/datalayer-trackscroll-init.es6.js @@ -0,0 +1,16 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +import TrackScroll from './datalayer-trackscroll.es6'; + +// Create namespace +if (typeof window.Mozilla === 'undefined') { + window.Mozilla = {}; +} + +window.Mozilla.TrackScroll = TrackScroll; + +window.addEventListener('scroll', TrackScroll.onScroll, false); diff --git a/media/js/base/datalayer-trackscroll.es6.js b/media/js/base/datalayer-trackscroll.es6.js new file mode 100644 index 0000000000..361fd10599 --- /dev/null +++ b/media/js/base/datalayer-trackscroll.es6.js @@ -0,0 +1,85 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +if (typeof window.dataLayer === 'undefined') { + window.dataLayer = []; +} + +const TrackScroll = {}; + +// track when page has been scrolled these percentages: +let thresholds = [25, 50, 75, 90]; +let listening = true; + +function debounce(func, delay) { + let timer; + return function () { + clearTimeout(timer); + timer = setTimeout(() => { + func.apply(this, arguments); + }, delay); + }; +} + +// get what percentage of the page has been scrolled +TrackScroll.getDepth = (scrollHeight, innerHeight, scrollY) => { + return (scrollY / (scrollHeight - innerHeight)) * 100; +}; + +// log the event to the dataLayer +TrackScroll.sendEvent = (threshold) => { + window.dataLayer.push({ + event: 'scroll', + percent_scrolled: String(threshold) + }); +}; + +// removes the event listener after we're done +TrackScroll.removeListener = () => { + if (listening) { + window.removeEventListener('scroll', TrackScroll.scrollListener, false); + listening = false; + } +}; + +TrackScroll.scrollHandler = () => { + // check the browser supports filter before doing anything else + if (typeof Array.prototype.filter === 'function') { + const scrollHeight = document.documentElement.scrollHeight; + const innerHeight = window.innerHeight; + const scrollY = window.scrollY; + const currentDepth = TrackScroll.getDepth( + scrollHeight, + innerHeight, + scrollY + ); + + // get a list of thresholds we've passed + const matchingThresholds = thresholds.filter( + (threshold) => currentDepth >= threshold + ); + + // remove thresholds we've passed from list of ones we're looking for + thresholds = thresholds.filter((threshold) => currentDepth < threshold); + + // send the event for thresholds we passed + matchingThresholds.forEach((threshold) => { + TrackScroll.sendEvent(threshold); + }); + + // remove the event listener if we've scrolled past all thresholds + if (thresholds.length === 0) { + TrackScroll.removeListener(); + } + } else { + // if it's too old to support logging, remove the listener + TrackScroll.removeListener(); + } +}; + +TrackScroll.onScroll = debounce(TrackScroll.scrollHandler, 100); + +export default TrackScroll; diff --git a/media/static-bundles.json b/media/static-bundles.json index 76fe82a726..4efdc10e01 100644 --- a/media/static-bundles.json +++ b/media/static-bundles.json @@ -1588,7 +1588,8 @@ "files": [ "js/base/experiment-amo.es6.js", "js/base/experiment-amo-init.es6.js", - "js/base/datalayer-productdownload-init.es6.js" + "js/base/datalayer-productdownload-init.es6.js", + "js/base/datalayer-trackscroll-init.es6.js" ], "name": "data" }, diff --git a/tests/unit/spec/base/datalayer-trackscroll.js b/tests/unit/spec/base/datalayer-trackscroll.js new file mode 100644 index 0000000000..2bb377cd9a --- /dev/null +++ b/tests/unit/spec/base/datalayer-trackscroll.js @@ -0,0 +1,71 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* For reference read the Jasmine and Sinon docs + * Jasmine docs: https://jasmine.github.io/ + * Sinon docs: http://sinonjs.org/docs/ + */ + +import TrackScroll from '../../../../media/js/base/datalayer-trackscroll.es6.js'; + +describe('datalayer-trackscroll.es6.js', function () { + const scrollHeight = 1000; + const innerHeight = 200; + const scroll25 = 200; + const scroll50 = 400; + const scroll75 = 600; + const scroll90 = 720; + + beforeEach(function () { + window.dataLayer = []; + }); + + afterEach(function () { + delete window.dataLayer; + }); + + it('calculates scroll depth correctly', function () { + const depth25 = TrackScroll.getDepth( + scrollHeight, + innerHeight, + scroll25 + ); + expect(depth25 === 25).toBeTruthy(); + const depth50 = TrackScroll.getDepth( + scrollHeight, + innerHeight, + scroll50 + ); + expect(depth50 === 50).toBeTruthy(); + const depth75 = TrackScroll.getDepth( + scrollHeight, + innerHeight, + scroll75 + ); + expect(depth75 === 75).toBeTruthy(); + const depth90 = TrackScroll.getDepth( + scrollHeight, + innerHeight, + scroll90 + ); + expect(depth90 === 90).toBeTruthy(); + }); + + it('correctly identifies when multiple scroll thresholds have been passed', function () { + spyOn(TrackScroll, 'getDepth').and.returnValue(80); + TrackScroll.scrollHandler(); + expect(window.dataLayer[0]['percent_scrolled'] === '25').toBeTruthy(); + expect(window.dataLayer[1]['percent_scrolled'] === '50').toBeTruthy(); + expect(window.dataLayer[2]['percent_scrolled'] === '75').toBeTruthy(); + expect(window.dataLayer[4]).toBeFalsy(); + }); + + it('will append the scroll event to the dataLayer', function () { + TrackScroll.sendEvent('50'); + expect(window.dataLayer[0]['event'] === 'scroll').toBeTruthy(); + expect(window.dataLayer[0]['percent_scrolled'] === '50').toBeTruthy(); + }); +});