滚动后加载内联JavaScript以优化LCP问题求助
解决Quantcast Choice脚本导致的LCP过高问题
问题背景
页面Largest Contenful Paint(LCP)数值过高,经排查由Quantcast Choice(TCF 2.0合规管理标签v2.0)脚本导致。已尝试使用defer属性、通过DOMContentLoaded事件加载的方式,但仍希望实现用户滚动哪怕1像素后再加载该脚本,完全规避其对LCP的影响。
原脚本如下:
<!-- Quantcast Choice. Consent Manager Tag v2.0 (for TCF 2.0) --> <script type="text/javascript" async=true> (function() { var host = window.location.hostname; var element = document.createElement('script'); var firstScript = document.getElementsByTagName('script')[0]; var url = 'https://cmp.quantcast.com' .concat('/choice/', 'h05nHPpABpSs3', '/', host, '/choice.js'); var uspTries = 0; var uspTriesLimit = 3; element.async = true; element.type = 'text/javascript'; element.src = url; firstScript.parentNode.insertBefore(element, firstScript); function makeStub() { var TCF_LOCATOR_NAME = '__tcfapiLocator'; var queue = []; var win = window; var cmpFrame; function addFrame() { var doc = win.document; var otherCMP = !!(win.frames[TCF_LOCATOR_NAME]); if (!otherCMP) { if (doc.body) { var iframe = doc.createElement('iframe'); iframe.style.cssText = 'display:none'; iframe.name = TCF_LOCATOR_NAME; doc.body.appendChild(iframe); } else { setTimeout(addFrame, 5); } } return !otherCMP; } function tcfAPIHandler() { var gdprApplies; var args = arguments; if (!args.length) { return queue; } else if (args[0] === 'setGdprApplies') { if ( args.length > 3 && args[2] === 2 && typeof args[3] === 'boolean' ) { gdprApplies = args[3]; if (typeof args[2] === 'function') { args[2]('set', true); } } } else if (args[0] === 'ping') { var retr = { gdprApplies: gdprApplies, cmpLoaded: false, cmpStatus: 'stub' }; if (typeof args[2] === 'function') { args[2](retr); } } else { if(args[0] === 'init' && typeof args[3] === 'object') { args[3] = { ...args[3], tag_version: 'V2' }; } queue.push(args); } } function postMessageEventHandler(event) { var msgIsString = typeof event.data === 'string'; var json = {}; try { if (msgIsString) { json = JSON.parse(event.data); } else { json = event.data; } } catch (ignore) {} var payload = json.__tcfapiCall; if (payload) { window.__tcfapi( payload.command, payload.version, function(retValue, success) { var returnMsg = { __tcfapiReturn: { returnValue: retValue, success: success, callId: payload.callId } }; if (msgIsString) { returnMsg = JSON.stringify(returnMsg); } if (event && event.source && event.source.postMessage) { event.source.postMessage(returnMsg, '*'); } }, payload.parameter ); } } while (win) { try { if (win.frames[TCF_LOCATOR_NAME]) { cmpFrame = win; break; } } catch (ignore) {} if (win === window.top) { break; } win = win.parent; } if (!cmpFrame) { addFrame(); win.__tcfapi = tcfAPIHandler; win.addEventListener('message', postMessageEventHandler, false); } }; makeStub(); var uspStubFunction = function() { var arg = arguments; if (typeof window.__uspapi !== uspStubFunction) { setTimeout(function() { if (typeof window.__uspapi !== 'undefined') { window.__uspapi.apply(window.__uspapi, arg); } }, 500); } }; var checkIfUspIsReady = function() { uspTries++; if (window.__uspapi === uspStubFunction && uspTries < uspTriesLimit) { console.warn('USP is not accessible'); } else { clearInterval(uspInterval); } }; if (typeof window.__uspapi === 'undefined') { window.__uspapi = uspStubFunction; var uspInterval = setInterval(checkIfUspIsReady, 6000); } })(); </script> <!-- End Quantcast Choice. Consent Manager Tag v2.0 (for TCF 2.0) -->
实现方案
方案1:监听scroll事件(简单直接)
通过监听scroll事件,在用户首次滚动后加载脚本,并立即移除事件监听,避免重复触发。
<script> function loadQuantcastScript() { // 移除滚动监听,避免重复执行 window.removeEventListener('scroll', loadQuantcastScript); // 原Quantcast脚本逻辑 (function() { var host = window.location.hostname; var element = document.createElement('script'); var firstScript = document.getElementsByTagName('script')[0]; var url = 'https://cmp.quantcast.com' .concat('/choice/', 'h05nHPpABpSs3', '/', host, '/choice.js'); var uspTries = 0; var uspTriesLimit = 3; element.async = true; element.type = 'text/javascript'; element.src = url; firstScript.parentNode.insertBefore(element, firstScript); function makeStub() { var TCF_LOCATOR_NAME = '__tcfapiLocator'; var queue = []; var win = window; var cmpFrame; function addFrame() { var doc = win.document; var otherCMP = !!(win.frames[TCF_LOCATOR_NAME]); if (!otherCMP) { if (doc.body) { var iframe = doc.createElement('iframe'); iframe.style.cssText = 'display:none'; iframe.name = TCF_LOCATOR_NAME; doc.body.appendChild(iframe); } else { setTimeout(addFrame, 5); } } return !otherCMP; } function tcfAPIHandler() { var gdprApplies; var args = arguments; if (!args.length) { return queue; } else if (args[0] === 'setGdprApplies') { if ( args.length > 3 && args[2] === 2 && typeof args[3] === 'boolean' ) { gdprApplies = args[3]; if (typeof args[2] === 'function') { args[2]('set', true); } } } else if (args[0] === 'ping') { var retr = { gdprApplies: gdprApplies, cmpLoaded: false, cmpStatus: 'stub' }; if (typeof args[2] === 'function') { args[2](retr); } } else { if(args[0] === 'init' && typeof args[3] === 'object') { args[3] = { ...args[3], tag_version: 'V2' }; } queue.push(args); } } function postMessageEventHandler(event) { var msgIsString = typeof event.data === 'string'; var json = {}; try { if (msgIsString) { json = JSON.parse(event.data); } else { json = event.data; } } catch (ignore) {} var payload = json.__tcfapiCall; if (payload) { window.__tcfapi( payload.command, payload.version, function(retValue, success) { var returnMsg = { __tcfapiReturn: { returnValue: retValue, success: success, callId: payload.callId } }; if (msgIsString) { returnMsg = JSON.stringify(returnMsg); } if (event && event.source && event.source.postMessage) { event.source.postMessage(returnMsg, '*'); } }, payload.parameter ); } } while (win) { try { if (win.frames[TCF_LOCATOR_NAME]) { cmpFrame = win; break; } } catch (ignore) {} if (win === window.top) { break; } win = win.parent; } if (!cmpFrame) { addFrame(); win.__tcfapi = tcfAPIHandler; win.addEventListener('message', postMessageEventHandler, false); } }; makeStub(); var uspStubFunction = function() { var arg = arguments; if (typeof window.__uspapi !== uspStubFunction) { setTimeout(function() { if (typeof window.__uspapi !== 'undefined') { window.__uspapi.apply(window.__uspapi, arg); } }, 500); } }; var checkIfUspIsReady = function() { uspTries++; if (window.__uspapi === uspStubFunction && uspTries < uspTriesLimit) { console.warn('USP is not accessible'); } else { clearInterval(uspInterval); } }; if (typeof window.__uspapi === 'undefined') { window.__uspapi = uspStubFunction; var uspInterval = setInterval(checkIfUspIsReady, 6000); } })(); } // 添加滚动监听 window.addEventListener('scroll', loadQuantcastScript); </script>
方案2:使用IntersectionObserver(性能更优)
相比于scroll事件,IntersectionObserver是异步的,不会频繁触发,性能更友好。可以在页面底部添加一个不可见元素,当用户滚动到该元素附近(哪怕1像素滚动)时触发脚本加载。
<!-- 在页面底部添加不可见的触发元素 --> <div id="scroll-trigger" style="position: absolute; top: 1px; left: 0; width: 1px; height: 1px;"></div> <script> function loadQuantcastScript() { // 停止监听 observer.disconnect(); // 原Quantcast脚本逻辑 (function() { var host = window.location.hostname; var element = document.createElement('script'); var firstScript = document.getElementsByTagName('script')[0]; var url = 'https://cmp.quantcast.com' .concat('/choice/', 'h05nHPpABpSs3', '/', host, '/choice.js'); var uspTries = 0; var uspTriesLimit = 3; element.async = true; element.type = 'text/javascript'; element.src = url; firstScript.parentNode.insertBefore(element, firstScript); function makeStub() { var TCF_LOCATOR_NAME = '__tcfapiLocator'; var queue = []; var win = window; var cmpFrame; function addFrame() { var doc = win.document; var otherCMP = !!(win.frames[TCF_LOCATOR_NAME]); if (!otherCMP) { if (doc.body) { var iframe = doc.createElement('iframe'); iframe.style.cssText = 'display:none'; iframe.name = TCF_LOCATOR_NAME; doc.body.appendChild(iframe); } else { setTimeout(addFrame, 5); } } return !otherCMP; } function tcfAPIHandler() { var gdprApplies; var args = arguments; if (!args.length) { return queue; } else if (args[0] === 'setGdprApplies') { if ( args.length > 3 && args[2] === 2 && typeof args[3] === 'boolean' ) { gdprApplies = args[3]; if (typeof args[2] === 'function') { args[2]('set', true); } } } else if (args[0] === 'ping') { var retr = { gdprApplies: gdprApplies, cmpLoaded: false, cmpStatus: 'stub' }; if (typeof args[2] === 'function') { args[2](retr); } } else { if(args[0] === 'init' && typeof args[3] === 'object') { args[3] = { ...args[3], tag_version: 'V2' }; } queue.push(args); } } function postMessageEventHandler(event) { var msgIsString = typeof event.data === 'string'; var json = {}; try { if (msgIsString) { json = JSON.parse(event.data); } else { json = event.data; } } catch (ignore) {} var payload = json.__tcfapiCall; if (payload) { window.__tcfapi( payload.command, payload.version, function(retValue, success) { var returnMsg = { __tcfapiReturn: { returnValue: retValue, success: success, callId: payload.callId } }; if (msgIsString) { returnMsg = JSON.stringify(returnMsg); } if (event && event.source && event.source.postMessage) { event.source.postMessage(returnMsg, '*'); } }, payload.parameter ); } } while (win) { try { if (win.frames[TCF_LOCATOR_NAME]) { cmpFrame = win; break; } } catch (ignore) {} if (win === window.top) { break; } win = win.parent; } if (!cmpFrame) { addFrame(); win.__tcfapi = tcfAPIHandler; win.addEventListener('message', postMessageEventHandler, false); } }; makeStub(); var uspStubFunction = function() { var arg = arguments; if (typeof window.__uspapi !== uspStubFunction) { setTimeout(function() { if (typeof window.__uspapi !== 'undefined') { window.__uspapi.apply(window.__uspapi, arg); } }, 500); } }; var checkIfUspIsReady = function() { uspTries++; if (window.__uspapi === uspStubFunction && uspTries < uspTriesLimit) { console.warn('USP is not accessible'); } else { clearInterval(uspInterval); } }; if (typeof window.__uspapi === 'undefined') { window.__uspapi = uspStubFunction; var uspInterval = setInterval(checkIfUspIsReady, 6000); } })(); } // 创建IntersectionObserver实例 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { loadQuantcastScript(); } }); }, { threshold: 0.01 }); // 监听触发元素 const triggerElement = document.getElementById('scroll-trigger'); if (triggerElement) { observer.observe(triggerElement); } </script>
注意事项
- 两种方案都会在用户首次滚动后加载脚本,完全避免脚本在LCP计算周期内执行,从而消除其对LCP的影响。
- 方案2的
IntersectionObserver兼容性良好(支持Chrome 51+、Firefox 55+、Edge 15+),现代浏览器无需额外处理;若需兼容更老浏览器,可添加对应polyfill。
内容的提问来源于stack exchange,提问作者Jorge Fernando García
相关产品推荐
相关产品推荐

