我正在尝试根据用户交互来旋转轮子。如果用户正在加速滚动,滚轮应该旋转得更快。同样,如果用户正在减速他的滚动,滚轮应该停止。我使用 Skrollr 库将这些条件与样式组件和 React 状态一起应用。
这是我所拥有的:
import React from "react";
import styled, { createGlobalStyle, css, keyframes } from "styled-components";
export default function App() {
const [previousPosition, setPreviousPosition] = React.useState(0);
const [previousDelta, setPreviousDelta] = React.useState(0);
const [speed, setSpeed] = React.useState(1);
const [isStopping, setIsStopping] = React.useState(false);
React.useEffect(() => {
skrollr.init();
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [previousPosition, previousDelta]);
const handleScroll = React.useCallback(() => {
// get delta
const delta = window.pageYOffset - previousPosition;
if (previousDelta > delta) {
// is stopping
setIsStopping(true);
setSpeed(0);
} else {
// is accelerating
// calculate delta as a percentage
const deltaAsPercentage = (delta / previousPosition) * 100;
console.log("deltaAsPercentage", deltaAsPercentage);
setIsStopping(false);
setSpeed(deltaAsPercentage);
}
setPreviousPosition(window.pageYOffset);
}, [previousPosition, previousDelta]);
return (
<Container data-10000p="transform: translateX(0%)">
<GlobalStyles />
<WheelContainer speed={speed} isStopping={isStopping}>
<Image src="wheel.png" alt="wheel" />
</WheelContainer>
</Container>
);
}
const Container = styled.div`
position: fixed;
width: 100%;
display: flex;
top: 0;
left: 0;
`;
const spinForward = keyframes`
0% {
transform: rotateZ(0deg);
}
50% {
transform: rotateZ(180deg);
}
100% {
transform: rotateZ(360deg);
}
`;
const stopWheel = keyframes`
0% {
transform: rotateZ(0deg);
}
100% {
transform: rotateZ(360deg);
}
`;
const WheelContainer = styled.div`
${({ speed, isStopping }) =>
isStopping
? css`
animation: ${stopWheel} 1s ease-out;
`
: css`
animation: ${speed
? css`
${spinForward} ${speed}s linear infinite
`
: ""};
`}
`;
const Image = styled.img``;
const GlobalStyles = createGlobalStyle`
* {
&::-webkit-scrollbar {
display: none;
}
}
`;
我数学不是很好,所以我正在尽我所能。我在滚动时做的第一件事是确定轮子是否应该停止或加速。如果它正在停止,我改变组件的状态并让
WheelContainer
知道它应该换出当前的动画。如果轮子不应该停止,我会保留当前动画并改变旋转速度。无论如何,我已经得到了它的工作。我遇到的问题是它无法识别“较慢的滚动”。例如,用户可能正在快速或缓慢地滚动,但仍然在滚动。缓慢的滚动并不一定意味着它应该停止。
另一个问题是
spinBack
似乎永远不会被调用。即使是这样,我也无法弄清楚如何区分“较慢”的滚动和向后旋转。最后,我应该注意到加速滚动似乎只能在 Mac 的触控板上被识别。我只是插入了一个外接鼠标来测试它,它并没有像预期的那样旋转。
除此之外,我觉得还有更好的方法。也许与
Math.sin
相关或 Math.cos
,或者类似的东西,我应该在高中数学课上更加关注。当看起来有一种更简单的方法时,该组件感觉太笨重了。这是一个沙盒,
最佳答案
这是我的尝试:
对我来说,第一个问题是代码在输入输入时正在执行。感觉它需要一些时间延迟来计算函数。
我们将使用 setTimeout
.
第二:是的,你是对的。我们需要一个类似数学/三 Angular 函数的函数,对于非常小的值,它的值接近于零,而对于增加的值,它的值接近于 1。
第三是……嗯,这更多是个人的事情——不确定这是否是故意的,但我注意到 spinBack
一旦您滚动到顶部(即 window.pageYOffset = 0
),该功能将不起作用。
所以,而不是 scroll
事件监听器,我使用了 wheel
eventListener — 这样,我可以使用 deltaY
属性(property),看看它改变了多少。
第四,我将速度设置为时间所覆盖距离的函数。
最后:CSS 速度一开始对我来说是违反直觉的——出于某种原因, 的值越高。较慢 它旋转了!我一直想知道出了什么问题,直到我意识到我的愚蠢错误!😅
总结一下,我只会粘贴我更改的部分(我已在更改的地方添加了评论):
// == adding these
var scrollTimeoutVar = null, scrollAmount = 0, totalScrollAmount = 0, scrollTimes = [];
var theMathFunction = (x) => {
return x/(2 + x); // <- you can use a different value from '2' to determine how fast your want the curve to grow
};
export default function App() {
const [previousPosition, setPreviousPosition] = React.useState(0);
const [previousDelta, setPreviousDelta] = React.useState(0);
const [speed, setSpeed] = React.useState(1);
const [isStopping, setIsStopping] = React.useState(false);
const [isMovingForward, setIsMovingForward] = React.useState(true);
React.useEffect(() => {
skrollr.init();
window.addEventListener("wheel", handleScroll); // <- changed from 'scroll'
return () => window.removeEventListener("wheel", handleScroll); // <- changed from 'scroll'
}, [previousPosition, previousDelta, isStopping]);
const handleScroll = React.useCallback((wheelEvent) => { // this time, we're passing in a wheel event
// console.log("wheelEvent", wheelEvent); // if you'd like to see it, uncomment
// get deltaY, which will be our distance covered
scrollAmount += wheelEvent.deltaY; // this will be reset every time a new scroll is recorded
totalScrollAmount += scrollAmount; // this one will never be reset
// add all the times that occured in the scroll
scrollTimes.push((new Date()).getTime());
setIsStopping(false);
// adding this here, to cancel the setTimeout when the user is done
if(scrollTimeoutVar !== null){
clearTimeout(scrollTimeoutVar);
};
scrollTimeoutVar = setTimeout(() => {
if(!isStopping){
setIsStopping(true);
// const delta = window.pageYOffset - previousPosition; <- no longer need this
// get time difference
const timeDiff = scrollTimes[scrollTimes.length - 1] - scrollTimes[0]; // when the scroll stopped - when it started
// get direction
// since our scrollAmount can either be positive or negative depending on the direction the user scrolled...
// ...we can use the cumulative amount to determine direction:
const isMovingForward = totalScrollAmount > 0; // no longer window.pageYOffset > previousPosition;
// console.log("isMovingForward", isMovingForward); // if you'd like to see it, uncomment
setIsMovingForward(isMovingForward);
// calculate delta as a percentage
// const deltaAsPercentage = (delta / previousPosition) * 100; <- we no longer need this
// setSpeed(deltaAsPercentage); <- we no longer need this
// our speed will be the simple physics equation of change in distance divided by change in time
const current_speed = Math.abs(scrollAmount/timeDiff); // we're using the 'absolute' function, so the speed is always a positive value regardless of the direction of scroll
const trig_speed = theMathFunction(current_speed); // give it a value between 0 and 1
// here's the counter-intuitive part
let speedToSet = 0;
// if it's currently movingforward...
if(isMovingForward){
// should we go faster, or slower? in this case, subtract to go FASTER, add to go SLOWER
speedToSet = scrollAmount > 0 ? (speed - trig_speed) : (speed + trig_speed);
}
// if not, it will be the reverse (because we're going in the OPPOSITE direction)
else{
// in this case, subtract to go SLOWER, add to go FASTER
speedToSet = scrollAmount > 0 ? (speed + trig_speed) : (speed - trig_speed);
}
// console.log("speedToSet", speedToSet); // if you'd like to see it, uncomment
setSpeed(speedToSet);
// set it back to zero, pending a new mouse scroll
scrollAmount = 0;
scrollTimes = [];
}
},100);
}, [previousPosition, previousDelta, isStopping]);
使用上述方法,旋转将在用户滚动鼠标的任何方向上加速(并且在他们改变方向时会减速)。
关于javascript - 如何根据当前pageYOffset和previousPageYOffset计算动画的速度(以秒为单位)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65264259/