Starting from this article https://www.newmediacampaigns.com/blog/maintaining-great-site-performanc-using-zendesk-web-widget I have implemented a solution that significantly reduces the load time by at least 3 seconds (in Google Lighthouse).
I have created a fake button in the HTML that will load the Zendesk script and open the widget when clicked. It will also load a localStorage item that will prevent this from happening on subsequent page loads.
⚠️ Warning:
The code relies heavily on how the widget is currently implemented (for example it expects a #launcher
and a #webWidget
element to appear on the page), so it can break as soon as the original code changes, but at least we will have an improvement in the loading times until they fix it.
Here is the most important part of the code:
HTML Button
<button class="zendesk-button">
<span class="left-icon">
<!-- here you insert the icon -->
</span>
<span class="text">Chat</span>
</button>
JavaScript code
// select the button
const zendeskButton = document.querySelector('.zendesk-button');
// add the script to the page
const loadZendeskScript = () => {
const zenDeskScript = document.createElement("script");
zenDeskScript.id = "ze-snippet";
zenDeskScript.src = "https://static.zdassets.com/ekr/snippet.js?key=HERE_YOU_INSERT_YOUR_KEY";
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0] || document.getElementsByTagName('script')[0].parentNode).insertBefore(zenDeskScript, null);
};
// a poller that waits for a condition and executes a callback
const poller = (comparison, callback, timerStep = 250, maxTime = 5000) => {
// why setTimeout instead of setInterval
// https://mcmap.net/q/267764/-using-setinterval-to-do-simplistic-continuous-polling
let currTime = 0;
const checkCondition = () => {
// `comparison` is a function so the condition is always up-to-date
if (comparison() === true) {
callback();
} else if (currTime <= maxTime) {
currTime += timerStep;
setTimeout(checkCondition, timerStep);
}
};
checkCondition(); // calling the function
};
// load the script and execute a callback if provided
const loadZendeskChat = callback => {
loadZendeskScript();
if (callback) {
callback();
}
};
// this function opens the chat
const openZendeskChat = () => {
poller(
() => {
// check that zendesk-related functions are available
return typeof zE !== 'undefined';
},
() => {
// open the widget
zE('webWidget', 'open');
poller(
() => {
// check that the elements exist and that the opacity is already set to "1"
const launcher = document.querySelector('#launcher');
const webWidget = document.querySelector('#webWidget');
return launcher !== null && webWidget !== null && webWidget.style.opacity === '1';
},
() => {
// hide the fake button
zendeskButton.style.opacity = '0';
// save in localStorage
localStorage.setItem('zd_hasOpened', 'true');
}
);
}
);
};
// zendesk management
if (localStorage.getItem('zd_hasOpened')) {
// load the zendesk widget if we find that it was opened
loadZendeskChat();
} else {
// show the fake button if it's the first time it shows
zendeskButton.style.opacity = '1';
}
// This will run when the .zendesk-button element is clicked
zendeskButton.addEventListener('click', () => {
// add a 'Loading' text to the button, as the widget will take some time to load (between 1 and 2 seconds on my laptop)
zendeskButton.querySelector('.text').innerHTML = 'Loading';
// load the zendesk widget
// open the widget and hide the fake button
loadZendeskChat(openZendeskChat);
});
Regarding styles, I have pretty much copied the style in the original widget, converting ems to pixels, but one part I'd like to highlight is the focus style, because in my opinion it helps telling the user that something is happening.
.zendesk-button:focus {
outline: none;
box-shadow: inset 0 0 0 0.21429rem rgb(255 255 255 / 40%) !important;
}