I have been working on an application. There are multiple components on the page. The content inside them is scrollable. The expected functionality is when I scroll inside the component the hover effects on different elements should be disabled. After searching in the internet I have a working solution. I have created a HoverDisabler component like this,
import React, {useEffect} from 'react';
export const HoverDisabler = ({children}) => {
let timer = 0;
useEffect(() => {
document.addEventListener('scroll', () => {
clearTimeout(timer);
if(!document.body.classList.contains('hoverDisabled')){
document.body.classList.add('hoverDisabled');
}
timer = setTimeout(() => {
document.body.classList.remove('hoverDisabled');
},500);
}, true);
}, []);
return children;
}
The css for hoverDisabled is as follows,
.hoverDisabled {
pointer-events: 'none',
}
And I am wrapping my root App component with HoverDisabler like this,
<HoverDisabler><App /></HoverDisabler>
Now in all the component, if I start scrolling the hoverDisabled class is added in the body and it gets removed when I stop scrolling. Everything is working perfectly. I am curious if this is the correct approach of having this functionality? Is there any shortcomings of this approach or some problem I am overlooking because of my lack of experience?
Since scroll event is an expensive event you can add a debounce on your scroll event like this:
function debounce(method, delay) {
clearTimeout(method._tId);
method._tId= setTimeout(function(){
method();
}, delay);
}
function scrollFunction(){
clearTimeout(timer);
if(!document.body.classList.contains('hoverDisabled')){
document.body.classList.add('hoverDisabled');
}
timer = setTimeout(() => {
document.body.classList.remove('hoverDisabled');
},500);
}
document.addEventListener('scroll', function() {
debounce(scrollFunction, 100);
});
This will surely optimize your code, as it will only fire scroll function lesser number of times. Even though there may be other approaches to the problem you're trying to solve I'm just suggesting a way to optimize your current code.
Related
I'm trying to convert some static html/css site to react.js. In script tag there is this function that applies some css transition when the window loads everytime. Here's the function below:
window.addEventListener('load', function() {
document.querySelectorAll('.progress-custom-bar-fill').forEach(e => {
e.style.width = `${e.getAttribute('fill-progress')}%`;
})
})
<div
className={`progress-custom-bar-fill ${goodOrOk}`}
fill-progress={progress}
/>
I tried appyling style property directly to the div like this:
<div
style={{ width: `${progress}%` }}
className={`progress-custom-bar-fill ${goodOrOk}`}
fill-progress={progress}
/>
It worked, but it doesn't appy css transiton
I tried useEffect, it works only first time when the window loads, but when switching between pages it doesn't work. This is what I tried:
useEffect(() => {
function updateProgressBar() {
document.querySelectorAll('.progress-custom-bar-fill').forEach((e) => {
e.style.width = `${e.getAttribute('fill-progress')}%`;
});
}
window.addEventListener('load', updateProgressBar);
return () => window.removeEventListener('load', updateProgressBar);
});
How to make useEffect to rerender on load event? Or is there any other solution for this? Thanks in advance
I have an element that being conditionally rendered with v-if="isLogged", if a user is logged in:
<div
v-if="isLogged"
class="chatBlock"
ref="chat"
></div>
I'm trying to get scroll height of the chat reference in a mounted () function - this.$refs.logged.scrollHeight, but it's not possible, because if a user is not logged in, then this div won't be rendered on a mounting stage, so even if a user logs in - it won't work, because mounted stage already passed.
Is it possible to track element appearance in a DOM using watch method?
UPDATE
Added watcher, as Steven suggested below in a mounted ():
this.$store.watch(
(state) => {
return this.$store.getters.isLogged
},
(newValue, oldValue) => {
if (newValue) {
this.chatHeight = this.$refs.chat.scrollHeight
}
}
)
The accepted answer did not work for me. The watch does not guarantee that the element has already been rendered.
To make sure that the element is available, I had to use $nextTick
if (newValue) {
this.$nextTick(function () {
this.chatHeight = this.$refs.chat.scrollHeight
})
}
This will cause the code to be executed after the next DOM update cycle.
Add a watch to isLogged. When it is active, get your chat ref. You will also have to check on your mounted, so put your logic in a common function.
So in your component:
val componentOptions = {
methods: {
checkDiv() {
const chatDiv = this.$refs.chat
if (chatDiv) {
// your code here...
}
}
},
mounted() {
this.checkDiv();
},
watch: {
isLogged(value) {
if (value) {
this.checkDiv()
}
}
}
}
—-
You can also use v-show instead of v-if. That way your element will be rendered but hidden.
Switching to v-show, as suggested by Steven, worked for me. It was actually the better option because v-show is cheap to toggle, unlike v-if, see this answer: https://stackoverflow.com/a/44296275/2544357
If I call a Polymer debouncer from a button click it works perfectly. I click 5 times in less than 2 seconds, prints only one timestamp:
myProofOfConcept(){
this.__debouncer = Polymer.Debouncer.debounce(
.__debouncer,
Polymer.Async.timeOut.after(2000),
() => {
console.log("HEY " + Date.now());
});
}
But if I call the exact same method from a Polymer properties change observer, it will wait the required 2 second timeout, and then console print as many times as the observer calls it, even if only 1 millisecond apart.
Is there some external factor that I don’t know about, that is driving this difference in behavior?
Here's something to look for if this ever happens to you.
If you debounce a method within a web component...
and then you instantiate multiple instances of that component within your web app... it may appear to you that the debouncer is not working, when in fact it may be working, rather it is just working in multiple instances of the same web component.
I had a stray previous instance of the web component inside an entirely different page. Hadn't been used, or even noticed. I had just forgotten to remove it when I migrated it to a different page.
EDIT: Control for debouncing across all instances of the element and for property changed from multiple places (button clicks and setInterval).
Usage seems to be fine when applied like:
<dom-module id="my-element">
<template>
<style>
:host {
display: block;
}
</style>
[[prop]]
<button on-click="add">Test</button>
</template>
<script>
(function() {
let DEBOUNCED_METHOD;
HTMLImports.whenReady(function() {
class MyElement extends Polymer.Element {
static get is() { return 'my-element'; }
static get properties() {
return {
prop: {
value: 0,
type: Number,
observer: 'myProofOfConcept'
}
}
}
constructor() {
super();
let test = setInterval(() => {
this.add();
}, 400);
setTimeout(() => {
clearInterval(test);
}, 4500)
}
add() {
this.prop += 1;
}
myProofOfConcept(){
DEBOUNCED_METHOD = Polymer.Debouncer.debounce(
DEBOUNCED_METHOD,
Polymer.Async.timeOut.after(2000),
this.log);
}
log() {
console.log("HEY " + Date.now());
}
}
customElements.define(MyElement.is, MyElement);
});
}())
</script>
Hope that helps!
asked a similar question earlier but i've been stuck at this for a while.
i have a div that v-shows on certain events, for example when hovering over div1 and clicking on div2. i want to make so this div disappears when the mouse hasn't touched it for a certain amount of time, let's say three seconds.
my problems are that i cant use v-on:mouseleave (because the div appears without the mouse being on it) so i'd need something that after a certain delay toggles v-show on the div. is there something i'm missing? what should i be using?
thanks!
The simplest way is to use a component and add an event listener in the mounted hook that uses a timeout to set a flag attached to v-show, so:
Vue.component('my-deiv', {
template: `<template id="block"><div v-show="show">A div</div></template>`,
mounted(){
this.$el.addEventListener('mousemove', () => {
setTimeout( () => {
this.show = false;
}, 3000)
})
},
data(){
return{
show: true
}
}
});
Now whenever the mouse moves over the div it fires the timeout, here's the JSFiddle: https://jsfiddle.net/nb80sn52/
EDIT
If you want to hide the div when the mouse has not moved on it for a certain amount of time - but not when the mouse exits, then you can restart the timeout each time the mouse moves and then cancel it altogether when the mouse leaves, here's the updated version:
Vue.component('block', {
template: "#block",
mounted() {
this.timer = null;
this.$el.addEventListener('mousemove', () => {
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.show = false;
}, 3000)
})
this.$el.addEventListener('mouseleave', () => {
clearTimeout(this.timer);
})
},
data() {
return {
show: true
}
}
})
And the JSFiddle for that: https://jsfiddle.net/utaaatjj/
You could setTimeout in your component render function (or any other function) which would change this.visible = true to this.visible = false after a predefined period of time.
I am making a results screen which toggles between showing the user their best time/score and their latest time/score. I found a solution using this site but after leaving the website open for a few hours I saw that the timings had gone out of sync. I know that this is hard to test so I thought I would see if any experts on here could help me to optimize or fix my code.
CODEPEN
JSFIDDLE
JS
$(document).ready(function() {
setInterval( function() { resultsTransition(); }, 4000);
function resultsTransition() {
$('.latest-transition').fadeOut(500).delay(3500).fadeIn(500).delay(3500);
$('.best-transition').fadeIn(500).delay(3500).fadeOut(500).delay(3500);
}
});
I think your design could be improved (and the out-of-sync problem solved) by simply toggling the opacity of the elements in your resultsTransition method instead of starting a new sequence, which could interfere unpredictably with the interval.
Something like:
var latestTransitionElementVisible = true; //the initial state of your elements
setInterval(resultsTransition, 4000); //note you can just pass the function name
function resultsTransition() {
$('.latest-transition').fadeTo(500, latestTransitionElementVisible ? 0 : 1);
$('.best-transition').fadeTo(500, latestTransitionElementVisible ? 1 : 0);
latestTransitionElementVisible = !latestTransitionElementVisible ;
}
I guess whatever problem/issue you are facing is because of varying animation times .Try the following:
$(document).ready(function() {
setTimeout( function() { resultsTransition(); }, 4000);
function resultsTransition() {
if(!$('.latest-transition').is(':animated') && !$('.best-transition').is(':animated'))
{
$('.latest-transition').fadeOut(500).delay(3500).fadeIn(500).delay(3500);
$('.best-transition').fadeIn(500).delay(3500).fadeOut(500).delay(3500);
setTimeout( function() { resultsTransition(); }, 4000);
}
}
});