I have a method/function in React where i swipe to the left and right on click with a translate value from CSS. However, on render the screens are loaded, and will move to the left or right and translate value will be set correctly. But the problem is, when i try to resize the browser the calculation doesn't work.
I have tried creating a onClick={this.ResizeSlider} and from there run the function but doesn't work.
My code:
NextSlide = () => {
const {
currentIndex,
data
} = this.state;
const slideWidth = this.slideWidth();
// Exiting the method early if we are at the end of the images array.
// We also want to reset currentIndex and translateValue, so we return
// to the first image in the array.
if(currentIndex === data.length - 1) {
return this.setState({
currentIndex: 0,
translateValue: 0
})
}
// This will not run if we met the if condition above
this.setState(NextState => ({
currentIndex: NextState.currentIndex + 1,
translateValue: NextState.translateValue + -(slideWidth)
}));
}
slideWidth = () => {
const { slideClass } = this.state;
return document.querySelector(slideClass).clientWidth
}
You need to add an eventListener so you know when the screen size has changed.
Try something like this:
componentDidMount() {
this.dimensionsChange();
window.addEventListener("resize", this.dimensionsChange());
}
And you can access the width/height values in the dimensionsChange() method using:
var w = window.innerWidth;
var h = window.innerHeight;
Don't forget to remove the event listener when your component unmounts:
componentWillUnmount() {
window.removeEventListener("resize", this.dimensionsChange());
}
Related
I'm trying to do the following
On isAnimate: boolean state change, determine if trigger animate() or draw()
draw() works fine
I also need a button to reset the animation, which is handled with the state toReset: boolean.
When toReset is true, immediately set toReset to false and call animate()
The animation is just an SVG with a single <path> element with its d attribute changed constantly.
Here are the functions that are of relevance (in source code writing order):
// Animation function (the problem function)
const animate = useCallback(() => {
console.log("animation start");
clearSVGPath(pathElement.current);
scaleSVGToPaths(svgElement.current, paths);
function* generatorPath() {
for (const path of paths) {
yield path;
}
}
const gen = generatorPath();
const animationTickTime = animationSpeed < 50 ? 1000 / animationSpeed : 20;
const animationTickPaths =
animationSpeed < 50 ? 1 : Math.round((20 * animationSpeed) / 1000);
console.log("tick time: ", animationTickTime);
console.log("tick lines: ", animationTickPaths);
clearInterval(animationIntervalID.current);
console.log("cleared animation interval ID: ", animationIntervalID.current);
function onTick() {
console.log("interval start");
let { value, done } = gen.next();
let i = 1;
const values = [];
while (i < animationTickPaths && !done) {
values.push(value);
({ value, done } = gen.next());
i += 1;
}
if (value) {
values.push(value);
}
console.log(values);
addPathsToSVG(pathElement.current, values);
if (done) {
clearInterval(animationIntervalID.current);
console.log("done");
}
}
console.log("starting interval");
animationIntervalID.current = setInterval(onTick, animationTickTime);
console.log("set: animation interval ID: ", animationIntervalID.current);
}, [animationSpeed, paths]);
// Draw Instantly
const draw = useCallback(() => {
clearSVGPath(pathElement.current);
scaleSVGToPaths(svgElement.current, paths);
setSVGPaths(pathElement.current, paths);
}, [paths]);
// reset animation
useEffect(() => {
if (!toReset) {
return;
}
setToReset(false);
animate();
return () => clearInterval(animationIntervalID.current);
}, [toReset, animate, setToReset]);
// Decide whether to draw instantly or animate
useEffect(() => {
if (!system) {
return;
}
if (!isAnimate) {
draw();
return;
}
animate();
return () => clearInterval(animationIntervalID.current);
}, [isAnimate, system, animate, draw]);
The problem: Upon change of isAnimate -> true (fourth codeblock, then first codeblock happens), it is all fine. However, upon toReset -> true, the animate() function is called but setInterval doesn't work correctly, even if cleared beforehand. Sometimes onTick() only runs one time before not working again, sometimes onTick() doesn't run at all.
A console screenshot after isAnimate -> true and then toReset -> true:
console screenshot
I'm not really sure what causes this problem, and would like assistance.
Thanks.
I have an element with the name ".offer" class right above the footer inside the page. When I approach this element in the scroll movement, I want the sticky header to close.
I wrote a function in scroll event for this but I am curious about something.
When I create a function like the example below, are the variables in the function re-created in memory for each scroll move? Or what's the truth for performance in a simple page scroll event?
const stickyNearOfferClose = () => {
let scrollPos;
let header = document.querySelector("header");
const documentOffset = document.querySelector(".offer").offsetTop;
header.classList.toggle("sticky", window.scrollY);
scrollPos = window.scrollY;
if (scrollPos >= documentOffset) {
header.classList.add("hide");
} else {
header.classList.remove("hide");
}
};
window.addEventListener("scroll", function () {
stickyNearOfferClose();
});
In addition to the above question
When I want to use the above function for one or more scroll actions,
Should we use it define variables inside a function or within an object for both in terms of performance and usability?
I shared two different examples below. Which one do you think should be?
const Obj = {
scrollPos : null,
elHeader: document.querySelector("header"),
documentOffset: document.querySelector(".offer").offsetTop,
// otherfunction (){},
stickyNearOfferClose() {
Obj.elHeader.classList.toggle("sticky", window.scrollY);
if (Obj.scrollPos >= Obj.documentOffset) {
Obj.elHeader.classList.add("hide");
} else {
Obj.elHeader.classList.remove("hide");
}
},
// init(){}
};
window.addEventListener("scroll", function () {
Obj.scrollPos = window.scrollY;
requestAnimationFrame(Obj.stickyNearOfferClose);
});
// OR
const Obj = {
// variables
stickyNearOfferClose() {
let scrollPos;
const elHeader = document.querySelector("header");
const elOffer = document.querySelector(".offer");
const documentOffset = elOffer.offsetTop;
let stickyClose = () => {
elHeader.classList.toggle("sticky", window.scrollY);
if (scrollPos >= documentOffset) {
elHeader.classList.add("hide");
} else {
elHeader.classList.remove("hide");
}
};
window.addEventListener("scroll", () => {
scrollPos = window.scrollY;
requestAnimationFrame(stickyClose);
});
},
spyScrolling() {
let scrollPos;
const sections = document.querySelectorAll(".hero");
let scrollActiveUrl = () => {
for (let s in sections) {
if (
sections.hasOwnProperty(s) &&
sections[s].offsetTop <= scrollPos + 150
) {
const id = sections[s].id;
document.querySelector(".active").classList.remove("active");
document.querySelector(`a[href*=${id}]`).classList.add("active");
}
}
};
window.addEventListener("scroll", () => {
scrollPos = document.documentElement.scrollTop || document.body.scrollTop;
requestAnimationFrame(scrollActiveUrl);
});
}
init(){
this.stickyNearOfferClose();
this.spyScrolling()
}
};
Yes, the variables in the function will be recreated on every scroll events. But in your case, you can put some variables outside of the function to save a bit.
As per MDN, you shouldn't call expensive DOM manipulations directly in the function. Instead, it is recommended to throttle the event using requestAnimationFrame(), setTimeout(), or a CustomEvent. You can learn more at here.
Following is the improved example of your code:
let scrollPos;
const elHeader = document.querySelector("header");
const elOffer = document.querySelector(".offer");
// Note: I'm assuming that `documentOffset` don't need to be updated on every scroll events.
// Update it as `scrollPos` if it need to be updated on every scroll events.
const documentOffset = elOffter.offsetTop;;
const stickyNearOfferClose = () => {
// Note: Please move the following line in the scroll event
// if it's also needed to be called on every scroll events.
elHeader.classList.toggle("sticky", window.scrollY);
if (scrollPos >= documentOffset) {
elHeader.classList.add("hide");
} else {
elHeader.classList.remove("hide");
}
};
window.addEventListener("scroll", function () {
scrollPos = window.scrollY;
requestAnimationFrame(stickyNearOfferClose);
});
I want to create button to adjust/increase the props speed when my marker is moving, but in my case the speed work if the pause button clicked . what should i do if i want to click speed button and automatically update this.speed
I've declare speed prop in data and want to use in methods with setInterval
this is my data :
data: () => ({
speed: 1000
});
//my methods
moveMarker () {
this.mapLoop = setInterval(() => {
if (this.index + 1 === (this.coordinates.length)) {
this.isPlay = true
clearInterval(this.mapLoop)
new mapboxgl.Popup({ offset: 25 })
.setLngLat(this.newMarker._lngLat)
.setText('You are arrived')
.addTo(this.map)
}
const el = document.createElement('div')
el.className = 'marker'
console.log(el)
this.isPlay = false
this.map.panTo(this.coordinates[this.index])
const lat = this.coordinates[this.index][0]
const lng = this.coordinates[this.index][1]
this.newMarker.setLngLat([lat, lng])
this.newMarker.addTo(this.map)
this.index++
}, this.speed)
},
//trigger button function
forward () {
this.speed -= 500
},
You'll need to cancel the previous interval and re-start it with the new speed. For example
forward () {
clearInterval(this.mapLoop)
this.speed -= 500
this.moveMarker()
}
I have an element that is scrollable. I also have a function that scrolls to a specific position. I would like to call a function when the scrollTo is finished.
Plunkr example
var x = document.querySelector('.container');
$scope.scrollTo = function() {
x.scrollTo({
top: 300 ,
behavior: 'smooth'
});
};
// do something when scrollTo is finished
By checking the position of the element I am scrolling to and comparing that to the current scroll position of the container you can see when the scrolling action is finished.
function isScrollToFinished() {
const checkIfScrollToIsFinished = setInterval(() => {
if (positionOfItem === scrollContainer.scrollTop) {
// do something
clearInterval(checkIfScrollToIsFinished);
}
}, 25);
}
The interval checks if the position of the scroll container is equal to the position of the element I'm scrolling to. Then do a action and clear the interval.
I suggest checking that the scroll movement is gone.
Less parameter, no risk, and works for extremes positions.
let position = null
const checkIfScrollIsStatic = setInterval(() => {
if (position === window.scrollY) {
clearInterval(checkIfScrollIsStatic)
// do something
}
position = window.scrollY
}, 50)
I modified #DoiDor's answer to ensure that the position is rounded and include a timeout fallback just in case. Otherwise the position may never be reached exactly, and the promise would never resolve.
async function scrollToPosition(container, position) {
position = Math.round(position);
if (container.scrollTop === position) {
return;
}
let resolveFn;
let scrollListener;
let timeoutId;
const promise = new Promise(resolve => {
resolveFn = resolve;
});
const finished = () => {
container.removeEventListener('scroll', scrollListener);
resolveFn();
};
scrollListener = () => {
clearTimeout(timeoutId);
// scroll is finished when either the position has been reached, or 100ms have elapsed since the last scroll event
if (container.scrollTop === position) {
finished();
} else {
timeoutId = setTimeout(finished, 100);
}
};
container.addEventListener('scroll', scrollListener);
container.scrollTo({
top: position,
behavior: 'smooth',
});
return promise;
}
I'm using a solution similar to this:
function scrollElementTo(el, position) {
return new Promise((resolve) => {
const scrollListener = (evt) => {
if (typeof evt === 'undefined') {
return;
}
const target = evt.currentTarget;
if (target.scrollTop === position) {
target.removeEventListener('scroll', scrollListener);
resolve();
}
};
el.addEventListener('scroll', scrollListener);
// scroll to desired position (NB: this implementation works for
// vertical scroll, but can easily be adjusted to horizontal
// scroll as well)
el.scrollTo(0, position);
});
}
const scrollContainer = document.querySelector('#theScrollContainer');
// desired y coords for scroll
const yDesiredScroll = 234;
scrollElementTo(scrollContainer, yDesiredScroll).then(() => {
// do something here
});
There is an application that listens to scrolling the screen by the user.
So that's why if the scrolling is done with the mouse wheel, the data comes an order of magnitude slower than if you scroll the page with a space.
Server code is not laid out, there is no point.
Below is the application code.
#HostListener('window:scroll', ['$event']) checkScroll() {
if (!this.getdata) {
const componentPosition = this.el.nativeElement.offsetTop;
const componentHeight = this.el.nativeElement.getBoundingClientRect().height;
const scrollPosition = window.pageYOffset;
const windowheight = window.outerHeight;
const needposition = componentPosition + componentHeight - windowheight - 500;
if (scrollPosition >= needposition) {
this.getdata = true;
this.getMoreNew();
}
}
}
getMoreNew() {
if (!this.nomoredata) {
const city = this.city;
const find = this.find;
const last = this.persones[this.persones.length - 1]['orderbyname'];
this.httpClient.post<Array<any>>('/assets/api/index.php', {action: 'person', type: this.type, last, limit: this.limit, city, find })
.subscribe(
data => {
if (data.length === 0) {
this.nomoredata = true;
this.getdata = false;
} else {
this.persones = this.persones.concat(data);
this.getdata = false;
}
}
);
} else {
this.getdata = false;
}
}
See screenshot from devtools:
I am not entirely sure what the question is, but I think it's that your scroll is too slow and you want to have a way to help offload the computation for the event? You could try using a debounce to ignore the scrolling until the user is done.