I have two functions that do the same thing, but are used differently.
I currently have a popup that is triggered on actions based on device. On desktop, if the mouse leaves the page, it triggers the popup (function called exitUserMessage). On mobile, if the mouse is inactive for 10 seconds+ it triggers the popup (function called inactiveUserMessage). See below:
//display popup for inactive users
const inactiveUserMessage = () => {
//other code was here but deleted bc not relevant
return dismissModal();
};
//display popup for users leaving the page
function exitUserMessage() {
//other code was here but deleted bc not relevant
return dismissModal();
}
Here is where the functions are used, both separately for desktop and mobile:
if (width <= 480) {
// Start the timer on page load
window.addEventListener("load", () => {
timer = setTimeout(inactiveUserMessage, 10000);
});
document.addEventListener("mousemove", debounce(inactiveUserMessage, 10000), timer);
} else {
// Trigger popup for desktop
document.addEventListener("mouseleave", (e) => {
if (Evergage.cashDom("#abandon-cart-message").length === 0) {
return exitUserMessage();
}
});
}
inactiveUserMessage is used in the debounce function that delays it by 10 seconds, then triggers the popup to show.
exitUserMessage is returned when the mouse leaves the page for desktop. My question is both of these functions are doing the same thing above ^.
Any ways to simplify? I am fairly new to JavaScript so looking to learn best practice!
Something worth noting: these are tracked as separate actions in our dataLayer.
Thank you!
You can call the inactiveUserMessage variable like functions. If there are different parts in those functions, add a parameter like isDesktop to inactiveUserMessage and debounce functions . If true, run the relevant desktop code in if statement.
const inactiveUserMessage = (isDesktop) => {
if (isDestop) {
//Desktop
} else {
//Mobile
}
return dismissModal();
};
if (width <= 480) {
// Start the timer on page load
window.addEventListener("load", () => {
timer = setTimeout(inactiveUserMessage, 10000);
});
document.addEventListener("mousemove", debounce(inactiveUserMessage, false, 10000), timer);
} else {
// Trigger popup for desktop
document.addEventListener("mouseleave", (e) => {
if (Evergage.cashDom("#abandon-cart-message").length === 0) {
return inactiveUserMessage(true);
}
});
}
Related
I want to resize a header when the user scrolls, by adding/subtracting a class. But, I'm missing something. The script below fires repeatedly, re-adding the class, If a user slowly scrolls down the page to the offset point that triggers the function. It doesn't matter what time I set, the user has still scrolled to that location and stopped - triggering the script repeatedly. I tried with a basic debouncer, and that didn't work - I got similar issues.
Here's my script:
let throttlePause;
const throttle = (callback, time) => {
if (throttlePause) return;
throttlePause = true;
setTimeout(() => {
callback();
throttlePause = false;
}, time);
};
function scrollShrink() {
document.getElementById('wrapper').classList.toggle('page-scrolled', window.pageYOffset >= 20);
}
// run through throttler
window.addEventListener("scroll", () => {
throttle(scrollShrink, 200);
});
<script
id="ze-snippet"
src="https://static.zdassets.com/ekr/snippet.js?key=some_zendesk_key"
/>
I'm trying to optimize my web site performance. I've faced a big impact of third-party code to my performance, I think all my bundle has a lower size than zendesk code. How can I load it without impacting on the main thread? Should I use the async or defer tags? Or which approach is better for this case?
This seems to be an issue that tortures so many people without a clear solution.
What I managed to do it to reduce the block time by adding this configuration.
window.zESettings = {
webWidget: {
chat: {
connectOnPageLoad: false
}
}
};
ref https://developer.zendesk.com/embeddables/docs/widget/settings#connectonpageload
ps
I did a performance test to my zendesk helpdesk "domain.zendesk.com" and the results there were even worse
I came across this issue recently and made this hack using a function for loading the zendesk script only when you reached certain point of the doc. I know is kind of dirty but it works:
<script defer type="text/javascript">
(function($, win) {
$.fn.inViewport = function(cb) {
return this.each(function(i,el){
function visPx(){
var H = $(this).height(),
r = el.getBoundingClientRect(), t=r.top, b=r.bottom;
return cb.call(el, Math.max(0, t>0? H-t : (b<H?b:H)));
} visPx();
$(win).on("resize scroll", visPx);
});
};
}(jQuery, window));
$('#trigger_div').inViewport(function(px){
if(px) {
//zopim code
}
});
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://stackoverflow.com/questions/8682622/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;
}
I'm making a web app where a button's behavior is different if the user clicks vs holds the button. I have been experimenting with different timings and it got me wondering if there is any established standard for this kind of thing.
For clarification: I am wondering if there is an exact timing that is standard. Below is the code I am using with 150ms being the threshold for a hold.
function onMouseDown()
{
var holdTimeout = setTimeout(function()
{
//Hold code (also cancels click event)
}, 150);
var cancelHold = function()
{
clearTimeout(holdTimeout);
};
window.onmouseup = cancelHold;
}
function onClick()
{
//Click code
}
Answering exactly your question, hold becomes click. You could set the click event (it's release in fact), inside the mousedown event. Run the code below and try holding and release the mouse button.
document.getElementById("click").addEventListener('mousedown', (e) => {
var i = 0;
var int = setInterval(() => {
console.log("hold " + i++);//<-- actions when we hold the button
}, 200)
document.getElementById("click").addEventListener("click", () => {
clearInterval(int);
console.log("release")//<-- actions when we release the button
})
});
<div id="click">click</div>
In this case, if we hold the button less that 200 milliseconds, just the click (release) event is fired.
I have a setinterval that runes every 5 seconds. this works fine on page load.
I have the following scenarios:
Load page with interval (WORKS)
press button and load new content and stopp interval(WORKS)
Once the new content is no longer desiered, dissmiss it, return to first content and start interval again(DOES NOT WORK)
I have saftys suchs as events for window.blur that also stops the interval so that the browser does not commponsate for all the missing intervals if i would change tabs or something. Keep in mind that step 3 did not work BUT if i would after step 3 change a tab and then return to my original page(execute blur) the interval would start working again.
NOTE all content loading here exept page load is done with ajax calls.
My code:
initializing:
$.automation.worker.bindIntervalEvent("#TanksContent", "/Tank/GetTanks", function() {
$.automation.tanks.tableInit();
});
binding function:
bindIntervalEvent: function (target, url, callback) {
$(window)
.on("focus.mine",
function() {
$.automation.worker.setUpdateInterval(target, url, callback);
})
.on("blur",
function() {
$.automation.worker.stopUpdateInterval();
}).trigger("focus.mine");
}
interval function:
setUpdateInterval: function (target, url, callback) {
if ($.automation.globals.globalInterval.value.length === 0) {
$.automation.globals.globalInterval.value.push(window.setInterval(
function () {
var options = {
loadTarget: target
}
$.automation.worker.getView(url,
function() {
if (callback)
callback();
},
options);
},
5000));
}
}
the function that stops the interval:
stopUpdateInterval: function () {
if ($.automation.globals.globalInterval.value.length === 0)
return;
console.log("deleting");
for (var i = 0; i <= $.automation.globals.globalInterval.value.length; i++) {
window.clearInterval($.automation.globals.globalInterval.value[i])
$.automation.globals.globalInterval.value.splice(i, 1);
console.log($.automation.globals.globalInterval.value.length);
}
}
when stopping the interval i also remove the window bindings:
unBindIntervalEvent: function() {
$(window).off("focus.mine");
$(window).unbind("blur");
}
Back to step 3:
My sucess method in the callback to my getviewfunction is identical to what i execute in the beginning
code:
$(".updatelatest")
.on("click",
function () {
var _this = $(this);
var options = {
loadTarget:"#TanksContent"
}
$.automation.worker.getView("/Tank/GetTanks",
function (data) {
$(_this).switchClass("col-md-5", "col-md-1", 1000, function() {
$(_this).addClass("hidden");
$(".search").switchClass("col-md-5", "col-md-12", 1000, "easeInOutQuad");
})
$.automation.tanks.tableInit();
$.automation.worker.bindIntervalEvent("#TanksContent", "/Tank/GetTanks", function () {
$.automation.tanks.tableInit();
});
$(window).trigger("blur");
}, options);
});
but this does not start the interval. it is clearly initialized since it works when window.blur is executed for example when I change tab but for some reason this is not working beyond that.
i tried triggering the windows blur event and nothing happened, i tried triggering my custom window event "focuse.mine" but nothing happens.
I did not notice this while developing since I had firebug open and every time i checked scripts or css or the console the blur function was executed so I assumed that my code worked as intended but now that it is deployed I notice this.
My head is pounding beyond reason and I can't for figure out where I have gone wrong.
Well this was a fun one. I simply found that when calling the setUpdateInterval(); function directly it gave me the desiered result.
I realized that the reason I had them split like I did was becaouse of the blur event. "Focus.mine" is triggered to start the inteval again ocne a user comes back to the page.
I can do something such as the following every 30 seconds to reload the page, and the backend logic will determine which session have been invalidated:
setInterval(function () {
location.reload()
}, 30000);
However, how would I only run this 30s location.reload() if the user is not active? For example, how banks will have a user-timeout if the user has not been active on the page (which only starts counting after the user is 'inactive'). How would this be done?
One way is to track mousemoves. If the user has taken focus away from the page, or lost interest, there will usually be no mouse activity:
(function() {
var lastMove = Date.now();
document.onmousemove = function() {
lastMove = Date.now();
}
setInterval(function() {
var diff = Date.now() - lastMove;
if (diff > 1000) {
console.log('Inactive for ' + diff + ' ms');
}
}, 1000);
}());
First define what "active" means. "Active" means probably, sending a mouse click and a keystroke.
Then, design your own handler for these situations, something like this:
// Reseting the reload timer
MyActivityWatchdog.prototype.resetReloadTimer = function(event) {
var reloadTimeInterval = 30000;
var timerId = null;
...
if (timerId) {
window.clearInterval(timerId);
}
timerId = window.setInterval( reload... , reloadTimeInterval);
...
};
Then, make sure the necessary event handler will call resetReloadTimer(). For that, you have to look what your software already does. Are there key press handlers? Are there mouse movement handlers? Without knowing your code, registering keypress or mousemove on document or window and could be a good start:
window.onmousemove = function() {
...
activityWatchdog.resetReloadTimer();
...
};
But like this, be prepared that child elements like buttons etc. won't fire the event, and that there are already different event handlers. The compromise will be finding a good set of elements with registered handlers that makes sure "active" will be recognized. E.g. if you have a big rich text editor in your application, it may be enough to register only there. So maybe you can just add the call to resetReloadTimer() to the code there.
To solve the problem, use window blur and focus, if the person is not there for 30 seconds ,it will go in the else condition otherwise it will reload the page .
setTimeout(function(){
$(window).on("blur focus", function(e) {
var prevType = $(this).data("prevType");
if (prevType != e.type) { // reduce double fire issues
switch (e.type) {
case "blur":
$('div').text("user is not active on page ");
break;
case "focus":
location.reload()
break;
}
}
$(this).data("prevType", e.type);
})},30000);
DEMO : http://jsfiddle.net/rpawdg6w/2/
You can check user Session in a background , for example send AJAX call every 30 - 60 seconds. And if AJAX's response will be insufficient (e.g. Session expired) then you can reload the page.
var timer;
function checkSession() {
$.ajax({
url : 'checksession.php',
success: function(response) {
if (response == false) {
location.reload();
}
}
});
clearTimeout(timer);
timer = setTimeout(checkSession,30 * 1000);
}
checkSession();