javascript - 使用 Swiper 5 模拟 3d 轮子的自定义效果
问题描述
我需要用 12 个项目构建一个旋转木马,模拟一个无限旋转的 3d 轮子。需要明确的是,我需要精确地创建这种效果:
https://codepen.io/SitePoint/pen/yXWXaw(在这里找到)
但有了这些附加功能(尤其是在桌面和移动设备上):
- 幻灯片必须一步一步地跟随滑动,也就是说幻灯片应该在滑动时移动(就像 Swiper 一样)。
- 通过快速滑动,它应该滚动许多具有动量的幻灯片(就像 Swiper 一样
freeScroll
)。 - 然后,当轮子停止旋转时,它会捕捉到前滑轨(就像 Swiper 使用
freeModeSticky
and 所做的那样centeredSlides
),它是从用户那里选择的。 - 每次幻灯片更改(如事件
slideChanged
)(如 Swiper 所做的那样)时,我都需要回调。
由于所有这些原因,我认为Swiper 5.3.0将是一个很好的起点。
我尝试了各种解决方法,这个设置更好,但这loop: true
是一个糟糕的解决方法并导致问题(查看评论):
var swiper = new Swiper(el_class, {
slidesPerView: 1.5,
spaceBetween: 25,
centeredSlides: true,
grabCursor: true,
speed: 550,
loop: true, // <== repeat infinitely the 12 items. with fast scroll at the end of a cycle it waits a while before render the next cycle. Awful
loopAdditionalSlides: 10,
// Free mode
freeMode: true, // <== free scrolling. Good
freeModeMomentumRatio: 1,
freeModeMomentumVelocityRatio: 1.5,
freeModeMomentumBounceRatio: 1,
freeModeMinimumVelocity: 0.02,
freeModeSticky: true, // <== snap to the slides. Good
// Touch Resistance
resistanceRatio: 0.85,
// Prevent blurry texts
roundLengths: true,
});
绝对不是正确的方法。
我认为正确的方法是开发一个模拟轮子的自定义 Swiper effect
(如内置的cubeEffect
, coverflowEffect
, ...),而不使用loop:true
会导致问题。例如,这里有一个人创建了自己的自定义效果,然后他在 Swiper 的effect
属性中设置:
https ://codepen.io/paralleluniv3rse/pen/yGQjMv
...
effect: "myCustomTransition",
...
如何开发像我需要的 3d 轮子这样的自定义效果?
解决方案
我认为这就是您想要的:https ://codepen.io/mukyuu/pen/GRgPYqG 。
它几乎满足了您的条件,只是它没有使用 Swiper 5 和 snap。
- 它随着滑动的方向旋转。
- 通过快速滑动,它应该以动量滚动许多幻灯片(就像 Swiper 所做的那样)。
然后当轮子停止旋转时,它会卡在一个幻灯片上(就像 Swiper 一样)。- 在
ontouch
函数中有一个回调。
HTML:
<div class="carousel" id="wrapper">
<figure>
<img src="https://source.unsplash.com/7mUXaBBrhoA/800x533" alt="">
<img src="https://source.unsplash.com/bjhrzvzZeq4/800x533" alt="">
<img src="https://source.unsplash.com/EbuaKnSm8Zw/800x533" alt="">
<img src="https://source.unsplash.com/kG38b7CFzTY/800x533" alt="">
<img src="https://source.unsplash.com/nvzvOPQW0gc/800x533" alt="">
<img src="https://source.unsplash.com/mCg0ZgD7BgU/800x533" alt="">
<img src="https://source.unsplash.com/1FWICvPQdkY/800x533" alt="">
<img src="https://source.unsplash.com/VkwRmha1_tI/800x533" alt="">
</figure>
</div>
小号(CSS):
body {
margin: 0;
font-family: 'Roboto';
font-size: 16px;
display: flex;
flex-direction: column;
height: 100vh;
justify-content: center;
}
// Carousel configuration parameters
$n: 8;
$item-width: 400px;
$item-separation: 80px;
$viewer-distance: 500px;
// Derived variables
$theta: 2 * 3.141592653589793 / $n;
$apothem: 482.842712474619px;
.carousel {
padding: 20px;
perspective: $viewer-distance;
overflow: hidden;
display: flex;
flex-direction: column;
align-items: center;
> * {
flex: 0 0 auto;
}
figure {
cursor: grab;
margin: 0;
width: $item-width;
transform-style: preserve-3d;
transition: transform 0.5s;
transform-origin: 50% 50% (-$apothem);
img {
width: 100%;
box-sizing: border-box;
padding: 0 $item-separation / 2;
opacity: 0.9;
&:not(:first-of-type) {
position: absolute;
left: 0;
top: 0;
transform-origin: 50% 50% (-$apothem);
}
@for $i from 2 through $n {
&:nth-child(#{$i}) {
transform: rotateY(#{($i - 1) * $theta}rad);
}
}
}
}
nav {
display: flex;
justify-content: center;
margin: 20px 0 0;
button {
flex: 0 0 auto;
margin: 0 5px;
cursor: pointer;
color: #333;
background: none;
border: 1px solid;
letter-spacing: 1px;
padding: 5px 10px;
}
}
}
JS:
var
carousel = document.querySelector('.carousel'),
figure = carousel.querySelector('figure'),
nav = carousel.querySelector('nav'),
numImages = figure.childElementCount,
theta = 2 * Math.PI / numImages,
currImage = 0
;
// add touch detect:
function ontouch(el, callback){
// Modified from http://www.javascriptkit.com/javatutors/touchevents3.shtml
var touchsurface = el,
dir,
swipeType,
startX,
startY,
distX,
distY,
threshold = 150, //required min distance traveled to be considered swipe
restraint = 100, // maximum distance allowed at the same time in perpendicular direction
allowedTime = 500, // maximum time allowed to travel that distance
elapsedTime,
startTime,
handletouch = callback || function(evt, dir, phase, swipetype, distance){}
touchsurface.addEventListener('touchstart', function(e){
var touchobj = e.changedTouches[0]
dir = 'none'
swipeType = 'none'
dist = 0
startX = touchobj.pageX
startY = touchobj.pageY
startTime = new Date().getTime() // record time when finger first makes contact with surface
handletouch(e, 'none', 'start', swipeType, 0) // fire callback function with params dir="none", phase="start", swipetype="none" etc
e.preventDefault()
}, false)
touchsurface.addEventListener('touchmove', function(e){
var touchobj = e.changedTouches[0]
distX = touchobj.pageX - startX // get horizontal dist traveled by finger while in contact with surface
distY = touchobj.pageY - startY // get vertical dist traveled by finger while in contact with surface
if (Math.abs(distX) > Math.abs(distY)){ // if distance traveled horizontally is greater than vertically, consider this a horizontal movement
dir = (distX < 0)? 'left' : 'right'
handletouch(e, dir, 'move', swipeType, distX) // fire callback function with params dir="left|right", phase="move", swipetype="none" etc
}
else{ // else consider this a vertical movement
dir = (distY < 0)? 'up' : 'down'
handletouch(e, dir, 'move', swipeType, distY) // fire callback function with params dir="up|down", phase="move", swipetype="none" etc
}
e.preventDefault() // prevent scrolling when inside DIV
}, false)
touchsurface.addEventListener('touchend', function(e){
var touchobj = e.changedTouches[0]
elapsedTime = new Date().getTime() - startTime // get time elapsed
if (elapsedTime <= allowedTime){ // first condition for awipe met
if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint){ // 2nd condition for horizontal swipe met
swipeType = dir // set swipeType to either "left" or "right"
}
else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint){ // 2nd condition for vertical swipe met
swipeType = dir // set swipeType to either "top" or "down"
}
}
// Fire callback function with params dir="left|right|up|down", phase="end", swipetype=dir etc:
handletouch(e, dir, 'end', swipeType, (dir =='left' || dir =='right')? distX : distY)
e.preventDefault()
}, false)
}
function DoSomething(dir, distance) {
//modifiy this function for wheel rotation (prev/next) images
var momentum = 100; // modify this value for how much momentum expected to switch to next/prev images
switch (dir){
case 'left':
case 'right':
currImage+= Math.round(distance/momentum);
break;
}
figure.style.transform = `rotateY(${currImage * -theta}rad)`;
}
document.getElementById('wrapper').ondragstart = function() { return false; }; // prevent image dragged on mouse drag
window.addEventListener('load', function() {
var dir, phase, el = document.getElementById('wrapper'),
position = {
X: 0,
Y: 0
};
el.onmousedown = function(down) {
position.X = down.clientX;
position.Y = down.clientY;
};
el.onmouseup = function(up) {
distX = up.clientX - position.X; // get horizontal dist traveled by finger while in contact with surface
distY = position.Y - up.clientY; // get vertical dist traveled by finger while in contact with surface
if (Math.abs(distX) > Math.abs(distY)) { // if distance traveled horizontally is greater than vertically, consider this a horizontal movement
dir = (distX < 0) ? 'left' : 'right';
distance = distX;
} else { // else consider this a vertical movement
dir = (distY < 0) ? 'down' : 'up';
distance = distY;
}
dir = (distance == 0) ? 'none' : dir;
DoSomething(dir, distance); // simulate touch from mouse control
};
ontouch(el, function(evt, dir, phase, swipetype, distance){
// evt: contains original Event object
// dir: contains "none", "left", "right", "top", or "down"
// phase: contains "start", "move", or "end"
// swipetype: contains "none", "left", "right", "top", or "down"
// distance: distance traveled either horizontally or vertically, depending on dir value
if ( phase == 'end' && (dir =='left' || dir == 'right') ) // on succesful swipe
DoSomething(dir, distance);
})
}, false)
在 Android 9 和 Windows 10 浏览器中测试。
推荐阅读
- logic - 如何在 Coq 中证明 (~Q -> ~P) - > (P -> Q)
- amazon-web-services - Nginx 代理服务器在一个位置接收动态 proxy_pass 但不确定如何在后续位置提供它
- nlp - 如何修复 DeprecationWarning: Call to deprecated 错误
- python - conda环境有访问系统模块,如何防止?
- colors - Adobe Illustrator 颜色对应问题
- javascript - 将单个 for 循环变成嵌套循环
- reactjs - Laravel Mix ReactJS:如何仅在文件保存时而不是在每次击键后进行编译?
- javascript - 是否可以从 async/await 中分离两个值 [任何解决方案]
- sql - SQL Server:过滤行到列
- c# - 你如何在 Xamarin 的 firebase 中 PutAsync()