I am learning ES6 and I don't understand why my addEventListener not working (trigger only one time) when I use a function like this :
// Trigger only one time
window.addEventListener("scroll", this.scroll() );
But when I do this :
// working !!
window.addEventListener("scroll", (e) => {
let top = window.pageYOffset;
console.log(top + " vs " + this.offsetTop)
if (top >= this.offsetTop) {
this.el.classList.add('is-sticky');
} else {
this.el.classList.remove('is-sticky');
}
});
The full code can be find here : https://codepen.io/paallaire/pen/GQLzmg/?editors=0010#0
The statement:
window.addEventListener("scroll", this.scroll() );
Binds to the event the result of this.scroll(), which is a function call. Such invocation returns undefined, because the scroll method has no return statement:
scroll() {
let top = window.pageYOffset;
console.log(top + " vs " + this.offsetTop);
if (top >= this.offsetTop) {
this.el.classList.add('is-sticky');
} else {
this.el.classList.remove('is-sticky');
}
}
Correct way
Do NOT use:
window.addEventListener("scroll", this.scroll);
The code above will bind the this to window when the event triggers.
The CORRECT way to use is really:
window.addEventListener("scroll", (e) => {
this.scroll();
});
Or
window.addEventListener("scroll", this.scroll.bind(this));
Which, when the event is triggered, will have the code inside this.scroll have the this point to the current class (Sticky) instance.
Removing the event listener
To remove the event, call window.removeEventListener, but there's a caveat: removeEventListener must be called with the exact same argument used in addEventListener to remove the listener. In other words, to be able to remove you will have to do:
// save the function that will be bound to the event, so you can remove it later
this.scrollBoundFunction = this.scroll.bind(this);
window.addEventListener("scroll", this.scrollBoundFunction);
// and later
window.removeEventListener("scroll", this.scrollBoundFunction);
Related
Everyhing works fine, except I can't figure out how to unbind the events in stop().
Code updated
class Resizable {
go(drag_el, resize_el) {
this.resize_el = resize_el;
drag_el.addEventListener("mousedown", () => {
window.addEventListener("mousemove", this.resize.bind(this));
window.addEventListener("mouseup", () => {
window.removeEventListener("mousemove", this.resize.bind(this));
window.removeEventListener("mouseup", this.resize.bind(this));
});
});
}
resize(e) {
this.resize_el.style.width = e.clientX - this.resize_el.offsetLeft + "px";
}
}
A few notes
I prefer to keep the class things inside the class, no additional var outside it.
I accept ES5 and ES6 as well.
I've read that I could do window.mousemove.bind(() => { // Code }); but I don't get that to work.
For scenario as such, I usually prefer pass the object directly as listener to addEventListener: it makes everything easier IMVHO. You have just to implement the handleEvent method:
class Resizable {
// Keeping a reference to the elements it's easier for the events handling
constructor(drag_el, resize_el) {
this.drag_el = drag_el;
this.resize_el = resize_el;
// The entry point event handler is added in the constructor
// to avoid to add multiple listeners (e.g. in the original
// code we could have called `go` method multiple times).
this.drag_el.addEventListener("mousedown", () => {
// Here we add the object itself as event handler
window.addEventListener("mousemove", this);
window.addEventListener("mouseup", this);
});
}
resize(e) {
this.resize_el.style.width = e.clientX - this.resize_el.offsetLeft + "px";
}
// Here we're removing the object itself as event handler
stop() {
window.removeEventListener("mousemove", this, false);
window.removeEventListener("mouseup", this, false);
}
// Here we're handling the events: since we added the object
// itself, `this` is still pointing to our instance of `Resizable`
// so we can call its methods.
handleEvent(event) {
switch (event.type) {
case "mousemove":
this.resize(event);
break;
case "mouseup":
this.stop();
break;
}
}
}
This approach reduces the usage of bindings and arrow functions a lot.
Here you can find a codepen demo with this code running.
You can't remove listeners inside stop function because you don't know them references.
Mousemove event don't call resize function, it call anonymous function
e => {
this.resize(resize_el, e);
}
You can save address of anonymous function in variable and use it for removing listener, like this
class Resizable {
constructor() {
this.mouseMoveHandler = (e) => {
this.resize(resize_el, e);
};
this.mouseDownHandler = () => {
window.addEventListener("mousemove", this.mouseMoveHandler);
}
this.mouseUpHandler = () => {
window.removeEventListener("mousemove", this.mouseMoveHandler);
}
}
go(drag_el, resize_el) {
drag_el.addEventListener("mousedown", this.mouseDownHandler);
window.addEventListener("mouseup", this.mouseUpHandler);
}
resize(resize_el, e) {
resize_el.style.width = e.clientX - resize_el.offsetLeft + "px";
}
stop() {
window.removeEventListener("mousemove", this.mouseMoveHandler); // ?
// most likely you would like remove listeners from this.go
}
}
You can find more info in this article on Medium [removeEventListener() and anonymous function]
UPD (after question update):
.bind() create NEW function everytime, so you still can't remove event listener.
Check my fiddle here: https://jsfiddle.net/nvfcq80z/5/
somehow what saveli said is true you have to call same function reference(pointing to same function called inside the listener) when removing listener ,if you wanna far more explaining ask sure you can ask
class Resizable {
go(drag_el, resize_el) {
this.resize_el=resize_el;
drag_el.addEventListener("mousedown", () => {
$(window).bind("mousemove", this.resize);
$(window).bind("mouseup", this.stop);
});
}
resize= (e) =>{
this.resize_el.style.width = e.clientX - this.resize_el.offsetLeft + "px";;
}
stop =(event) =>{
$(window).unbind(event,this.stop);
$(window).unbind("mousemove",this.resize);
}
}
edit this one with js only no jquery
class Resizable {
go(drag_el, resize_el) {
this.resize_el=resize_el;
drag_el.addEventListener("mousedown", () => {
window.addEventListener("mousemove", this.resize);
window.addEventListener("mouseup", this.stop);
});
}
resize= (e) =>{
this.resize_el.style.width = e.clientX - this.resize_el.offsetLeft + "px";
}
stop =(event) =>{
window.removeEventListener(event,this.stop);
window.removeEventListener("mousemove",this.resize);
}
}
I have already accepted the working answer that I liked the most. However I think a combination of #SaveliTomac and #ZER0 would be the best answer.
Therefor I have picked the best from both worlds, at least in my opinion.
https://jsfiddle.net/nhLjozm7/
HTML
<aside class="sidebar1">
<div class="sidebar1-resize"></div>
</aside>
<div class="sidebar2">
<div class="sidebar2-resize"></div>
</div>
JS
class Resizable {
constructor(drag_el, resize_el) {
this.drag_el = drag_el;
this.resize_el = resize_el;
this.handlers();
this.events();
}
handlers() {
this.mouseMoveHandler = (e) => {
this.resize(this.resize_el, e);
};
this.mouseDownHandler = () => {
window.addEventListener("mousemove", this.mouseMoveHandler);
}
this.mouseUpHandler = () => {
window.removeEventListener("mousemove", this.mouseMoveHandler);
}
}
events() {
this.drag_el.addEventListener("mousedown", this.mouseDownHandler);
window.addEventListener("mouseup", this.mouseUpHandler);
}
resize(resize_el, e) {
resize_el.style.width = e.clientX - resize_el.offsetLeft + "px";
}
}
let drag1 = document.querySelector(".sidebar1-resize");
let element1 = document.querySelector(".sidebar1");
new Resizable(drag1, element1);
let drag2 = document.querySelector(".sidebar2-resize");
let element2 = document.querySelector(".sidebar2");
new Resizable(drag2, element2);
I like the more flat handlers structure by #SaveliTomac (no nesting of events).
I like how #ZER0 put the resize handle inside the aside instead of outside of it.
I like how both #ZER0 and #SaveliTomac solutions support multiple resizable areas.
I like the clean and readability code by #SaveliTomac.
I like the set of values directly into constructor by #ZER0.
I wrapped all handlers into a method.
I wrapped events into a method.
I changed the HTML a bit.
I show two resizable elements.
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 .. */
}
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]);
So I have this javascript on a project I'm working on:
<script type="text/javascript">
document.getElementById('contact').onmouseover = function ()
{
var w = 130;
function step()
{
if (w < 250)
{
middle.style.width = (w++) + "px";
setTimeout(step, 5);
}
}
setTimeout(step, 1500);
};
</script>
I want this to run only once. After it detects a mouseover, I want it to run the function and then never run again until the page refreshes. How would I accomplish this?
I'd either use jQuery's one method or if you want to use 'plain' JavaScript you could just remove the event after the function has been triggered. Here's an example:
// Create a named function for the mouseover event
function myFunc() {
// Remove the `myFunc` function event listener once `myFunc` is run
document.getElementById('contact').removeEventListener('mouseover', myFunc, false);
var w = 130;
function step() {
if (w < 250) {
middle.style.width = (w++) + "px";
setTimeout(step, 5);
}
}
setTimeout(step, 1500);
};
// Add an event listener to run the `myFunc` function on mouseover
document.getElementById('contact').addEventListener('mouseover', myFunc, false);
Note that if you have to support IE8 (or even earlier), you need to use ...attachEvent("onmouseover", myFunc) and detachEvent("onmouseover", myFunc); instead; you can tell by checking if the element has addEventListener:
var elm = document.getElementById('contact')
if (elm.addEventListener) {
// Use addEventListener
}
else {
// Use attachEvent
}
(Perhaps hidden away in a utility function.)
All you need to do is remove the event listener from within the listener (so that it will stop listening to the event). However, in order to remove the listener, you need a reference to it, so you can't do it with a predefined listener directly attached to mouseover. Instead, use addEventListener to attach the listener, keep the returned reference and then use removeEventListener to remove the listener from within the callback.
var contact = document.getElementById('contact');
contact.addEventListener('mouseover', tehlistener);
function tehlistener() {
// yada yada do whatever
// ...
// I think it's ok to use `this` here, but since this is so specific
// its better to be specific about which listener you want removed
contact.removeEventListener('mouseover', tehlistener);
};
Here's a link to the lovely MDN article on addEventListener.
If you are interested in using JQuery, there is a nice function called "one" that may be exactly what you need.
http://api.jquery.com/one/
Edit: Adding more code to show more of the solution:
$( "#contact" ).one( "mouseover", function() {
var w = 130;
function step()
{
if (w < 250)
{
middle.style.width = (w++) + "px";
setTimeout(step, 5);
}
}
setTimeout(step, 1500);
});
You could just overwrite the event handler
<script type="text/javascript">
document.getElementById('contact').onmouseover = function() {
var w = 130;
function step() {
if (w < 250) {
middle.style.width = (w++) + "px";
setTimeout(step, 5);
}
}
setTimeout(step, 1500);
this.onmouseover = null;//overwrite event handler with a blank callback
};
</script>
You can use a once function.
function once(fn){
var called = false;
return function(){
if (called) {
return;
}
called = true;
return fn.apply(this, arguments);
}
}
Example:
something.onmouseover = once(function(){
// this will happen only once
});
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);
});
};
});