Scroll event runs even though i have not scrolled - javascript

The console logs this sentence 'hello' immediately after i refresh the page even though i have not scrolled on my mouse.
const changeOpacity = () => {
window.onscroll = console.log('hello')}
changeOpacity();

window.onscroll = console.log('hello') - will immediately invoke the call to console.log
You should wrap the assignment in a function declaration, like this:
window.onscroll = () => { console.log('hello'); }
This way your function will be called each time a scroll event is detected. Here's an example of how this would work:
const changeOpacity = () => {
window.onscroll = e => { console.log(`hello ${e.detail}`); };
}
//Init the event handler
changeOpacity();
//Fire some scroll events to test our handler
for(let i = 0; i < 10; i += 1) {
window.dispatchEvent(new CustomEvent('scroll', { detail: `scroll event ${i}` }));
}

Related

Rate limiting calls to a function without an event listener (throttle)

I have a long infinite scroll page full of videos that I am refreshing a sticky ad on. On each scroll to a new video the URL is updated. Each URL update fires a callback that calls the refresh function. I would like to rate limit how fast the function can be fired so that refresh calls don't happen too fast if the user scrolls too quickly.
I have been able to get throttle working in a test environment while using an event listener for a button click instead of the URL change callback, but have been unable to find a way to make it work without an event listener.
Here's the base code, as you can see I need to rate-limit how fast refreshFirstSlot is called.
// Function which refreshes the first slot
var refreshFirstSlot = function () {
googletag.cmd.push(function () {
googletag.pubads().refresh([adSlot1]);
});
};
// UrlUpdate is called each time the URL updates
UrlUpdate = function (url, type) {
refreshFirstSlot();
};
// throttle code
const throttle = (callback, delay) => {
let throttleTimeout = null;
let storedEvent = null;
const throttledEventHandler = (event) => {
storedEvent = event;
const shouldHandleEvent = !throttleTimeout;
if (shouldHandleEvent) {
callback(storedEvent);
storedEvent = null;
throttleTimeout = setTimeout(() => {
throttleTimeout = null;
if (storedEvent) {
throttledEventHandler(storedEvent);
}
}, delay);
}
};
return throttledEventHandler;
};
// adding the refresh call
var returnedFunction = throttle(function () {
refreshFirstSlot();
}, 5000);
// final call
UrlUpdate = function (url, type) {
returnedFunction();
};
Where am I going wrong here?
You could do something like this :
load your page => call your ad
scroll your page => refresh your ad unless previous call has not been rendered / ended
and so on...
To do so, you can use Google Publisher Tag's events (see here). Here is a simple example :
var refreshReady = false;
//slotRequested
googletag.pubads().addEventListener('slotRequested', function(event) {
var slotId = event.slot.getSlotElementId();
if(slotId === adSlot1.getSlotElementId()) {
//every time adSlot1 is requested, we disable the refresh variable
refreshReady = false;
}
});
//slotRenderEnded
googletag.pubads().addEventListener('slotRenderEnded', function(event) {
var slotId = event.slot.getSlotElementId();
if(slotId === adSlot1.getSlotElementId()) {
//every time adSlot1 has been rendered, we enable the refresh variable
refreshReady = true;
}
});
var refreshFirstSlot = function () {
if(refreshReady) {
googletag.cmd.push(function () {
googletag.pubads().refresh([adSlot1]);
});
}
else {
console.log('not yet !');
}
};
UrlUpdate = function (url, type) {
refreshFirstSlot();
};
You could add a timeout to make sure the ad is not refreshed as soon as rendered (improve viewability)

How to correctly use the scroll eventListener in vanilla javascript?

How can I improve the code below?
I wish to use only one function to execute my code with the scroll event. I have something like this:
function scrollMenu() {
window.addEventListener("scroll", () => {
// do stuff
});
}
function scrollhabilities() {
let cont = 0;
window.addEventListener("scroll", () => {
// do stuff
};
});
}
This works, but I tried to improve the code to be less repetative, however it doesn't work:
const scrollmenu = () =>{}
const scrollhabilities = () =>{}
function scrollThings() {
window.addEventListener("scroll", () => {
scrollmenu();
scrollhabilities():
});
}
Maybe this helps you to get a starting point. (you can obviously remove the event parameter if you never need it inside the function)
var scrollmenu = function(scrollEvent) {
console.log('scrollmenu', window.scrollY)
};
var scrollhabilities = function(scrollEvent){
console.log('scrollhabilities', window.scrollY)
};
var scrollThings = function () {
window.addEventListener("scroll", event => {
scrollmenu(event);
scrollhabilities(event);
});
}();
div.scroll {
background-color: orange;
height: 1000px;
width: 400px;
}
<div class="scroll">move your cursor here and scroll up and down</div>
You might need a debounce function. Debouncing allows your function to fire only once in the specified period of time (500ms in the below example).
Without this function, scrolling the window will fire thousands of times during a continuous scroll. With this function, it will fire only once. As soon as the user pauses for the specified period of time (eg. 500ms), the function will fire. (I set the timeout to 500ms to ensure the effect is seen - usually you would not specify such a large number - perhaps 100ms or less might be better.)
Note that there is a companion function called "throttle" that is slightly different.
var cnt = 0;
const msg = document.getElementById('msg');
window.addEventListener("scroll", () => {
handleScroll();
});
const handleScroll = debounce(() => {
cnt++;
msg.innerText = cnt;
},500);
function debounce(func, wait, immediate) {
var timeout;
return function() {
var context = this, args = arguments;
var later = function() {
timeout = null;
if (!immediate) func.apply(context, args);
};
var callNow = immediate && !timeout;
clearTimeout(timeout);
timeout = setTimeout(later, wait);
if (callNow) func.apply(context, args);
};
};
body{height:3000px;}
#msg{
position:fixed;
top:20px;
right:2px;
padding:2vh 5vw;
font-size:5rem;
background:wheat;
border:1px solid orange;
}
<div id="msg"></div>
References:
https://davidwalsh.name/javascript-debounce-function
this comment explains throttle
https://css-tricks.com/debouncing-throttling-explained-examples/

Removing eventHandler in function called by addEventListener

I added an infinite scrolling feature to my page. It attaches an event listener in the componentDidMount lifecycle hook and I want to remove it within the action called by the event listener when there is no "nextlink anymore". I set a console.log() message which works fine, but I am uncertain why the window.removeEventListener() function does not work. Any help would be appreciated.
Piece of code responsible for adding/removing the eventListener.
componentDidMount() {
this._isMounted = true;
this.props.onFetchTeams();
this.scrollListener = window.addEventListener("scroll", e => {
this.handleScroll(e);
});
}
handleScroll = () => {
const hasMoreLink = this.props.teams["#odata.nextLink"];
if (hasMoreLink == "") {
console.log("remove event handler");
window.removeEventListener("scroll", this.handleScroll);
}
// If there is at least a team and is currently not loading, proceed to load more.
if (this.state.loadingMore === false && this.props.teams["value"]) {
// get the last teamcard in the page
const lastTeam = document.querySelector(
".team-card-wrapper:last-of-type"
);
// get the height of the current team, and get the height of the current position on screen.
const lastTeamOffset = lastTeam.offsetTop + lastTeam.clientHeight;
const pageOffset = window.pageYOffset + window.innerHeight;
// the range that teams will load earlier than the bottom of the page.
const bottomOffset = 30;
if (pageOffset > lastTeamOffset - bottomOffset) {
this.setState({ loadingMore: true });
this.props.onFetchMoreTeams(hasMoreLink);
}
}
};
removeListener needs the same reference for function that it used while addListener. Change the code to addEventListener like
this.scrollListener = window.addEventListener("scroll", this.handleScroll);
This is because the function that is given to addEventListener and the one given to the removeEventListener should be exactly the same, but in your case, you are creating a new arrow function for the addEventListener. so I think you should try something like this:
this.scrollListener = window.addEventListener("scroll", this.handleScroll)
...
handleScroll = (e) => {
...
if(noMoreScroll) window.removeEventListener("scroll", this.handleScroll)
...
}
I hope this helps you :)
Consider revising the way your add the scroll event handler, by passing the handleScroll function directly:
componentDidMount() {
this._isMounted = true;
this.props.onFetchTeams();
/*
With the this.handleScroll bound to this class instance, we can now pass the method
directly to addEventListener as shown
*/
this.scrollListener = window.addEventListener("scroll", this.handleScroll);
}
handleScroll = () => {
const hasMoreLink = this.props.teams["#odata.nextLink"];
if (hasMoreLink == "") {
console.log("remove event handler");
/* This will now work as expected */
window.removeEventListener("scroll", this.handleScroll);
}
/* Rest of your code remains unchanged .. */
}

How to remove scroll event listener?

I am trying to remove scroll event listener when I scroll to some element. What I am trying to do is call a click event when some elements are in a viewport. The problem is that the click event keeps calling all the time or after first call not at all. (Sorry - difficult to explain) and I would like to remove the scroll event to stop calling the click function.
My code:
window.addEventListener('scroll', () => {
window.onscroll = slideMenu;
// offsetTop - the distance of the current element relative to the top;
if (window.scrollY > elementTarget.offsetTop) {
const scrolledPx = (window.scrollY - elementTarget.offsetTop);
// going forward one step
if (scrolledPx < viewportHeight) {
// console.log('section one');
const link = document.getElementById('2');
if (link.stopclik === undefined) {
link.click();
link.stopclik = true;
}
}
// SECOND STEP
if (viewportHeight < scrolledPx && (viewportHeight * 2) > scrolledPx) {
console.log('section two');
// Initial state
let scrollPos = 0;
window.addEventListener('scroll', () => {
if ((document.body.getBoundingClientRect()).top > scrollPos) { // UP
const link1 = document.getElementById('1');
link1.stopclik = undefined;
if (link1.stopclik === undefined) {
link1.click();
link1.stopclik = true;
}
} else {
console.log('down');
}
// saves the new position for iteration.
scrollPos = (document.body.getBoundingClientRect()).top;
});
}
if ((viewportHeight * 2) < scrolledPx && (viewportHeight * 3) > scrolledPx) {
console.log('section three');
}
const moveInPercent = scrolledPx / base;
const move = -1 * (moveInPercent);
innerWrapper.style.transform = `translate(${move}%)`;
}
});
You can only remove event listeners on external functions. You cannot remove event listeners on anonymous functions, like you have used.
Replace this code
window.addEventListener('scroll', () => { ... };
and do this instead
window.addEventListener('scroll', someFunction);
Then move your function logic into the function
function someFunction() {
// add logic here
}
You can then remove the click listener when some condition is met i.e. when the element is in the viewport
window.removeEventListener('scroll', someFunction);
Instead of listening to scroll event you should try using Intersection Observer (IO) Listening to scroll event and calculating the position of elements on each scroll can be bad for performance. With IO you can use a callback function whenever two elements on the page are intersecting with each other or intersecting with the viewport.
To use IO you first have to specify the options for IO. Since you want to check if your element is in view, leave the root element out.
let options = {
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
Then you specify which elements you want to watch:
let target = slideMenu; //document.querySelector('#oneElement') or document.querySelectorAll('.multiple-elements')
observer.observe(target); // if you have multiple elements, loop through them to add observer
Lastly you have to define what should happen once the element is in the viewport:
let callback = (entries, observer) => {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
});
};
You can also unobserve an element if you don't need the observer anymore.
Check this polyfill from w3c to support older browsers.
Here is my scenario/code, call removeEventListener as return() in the useEffect hook.
const detectPageScroll = () => {
if (window.pageYOffset > YOFFSET && showDrawer) {
// do something
}
};
React.useEffect(() => {
if (showDrawer) {
window.addEventListener("scroll", detectPageScroll);
}
return () => {
window.removeEventListener("scroll", detectPageScroll);
};
}, [showDrawer]);

Behavior of removeEventListener

Please check the below code:
var clickfn = function(){
alert("clicked");
}
document.getElementById("div1").addEventListener("click",clickfn,true);
clickfn = function(){ };
document.getElementById("div1").removeEventListener("click");
http://jsfiddle.net/qUtzL/4/
Why does the removeEventListener does not work?
removeEventListener takes 2 parameters, the event, and the function to remove.
This should work:
document.getElementById("div1").removeEventListener("click", clickfn);
Also, the function you're executing is empty.
var clickfn = function(){ };
You have to specify the exact function you've specified to addEventListener as the second argument. If you specified the third useCapture argument, you'll have to specify the same and equivalent to removeEventListener as well.
For example:
function myFunc(event){ alert(event.target.textContent); }
var myElement=document.getElementById('myElement');
//Add EventListener
myElement.addEventListener('click', myFunc, false );
/* ... */
//Remove EventListener
myElement.removeEventListener('click', myFunc, false );
↪ View an example at jsFiddle
You can find more information at the Mozilla Developer wiki.
I recently had this issue with the Navbar code in ReactJS to give the Navbar a background color after scrolling 100px on the y-axis and remove it if the page view is within 100px of the top.
All I had to do is introduce a reverse function in the removeEventListener to give it the rules for application.
const [show, handleShow] = useState(false);
useEffect(() => {
window.addEventListener('scroll', () => {
if (window.scrollY > 100) {
// do this
handleShow(true);
} else handleShow(false);
});
return () => {
window.removeEventListener('scroll', () => {
if (window.scrollY < 100) {
// do this
handleShow(false);
} else handleShow(true);
});
};
});

Categories

Resources