# 轮播
说下简单的思路吧, 就是一般来说首尾是一样的元素, 当最后一个一样的元素动画完成之后, 把元素的定位设置成初始定位, 然后重新开始动画, 轮播的定时可以使用setInterval
各种宽高之类的动态计算都可以封装起来。动画过度是匀速还是变速,这个步进值也可以自己设置。
# 1、简单的无缝滚动
不用js只是用css来实现,注意第一个和最后一个节点是一样的即可。translateX
滚动的距离应该是length-1乘以子项的宽度。
示例如下,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<style>
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
@keyframes Scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-1200px);
}
}
.container {
width: 300px;
height: 200px;
box-sizing: content-box;
border: 1px solid #ccc;
overflow: hidden;
margin: 100px auto;
}
.container > ul {
width: 1500px;
overflow: hidden;
list-style: none;
animation-name: Scroll;
animation-duration: 4s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
.container:hover > ul {
animation-play-state: paused;
}
.container > ul > li {
/* font-size: 0; */
width: 300px;
height: 200px;
float: left;
}
.container > ul > li:nth-child(2n) {
background-color: #aaa;
}
.container > ul > li:nth-child(2n-1) {
background-color: #ccc;
}
</style>
<div class="container" id="scroll">
<!-- 1800 - 600 = 1200 -->
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>1</li>
</ul>
</div>
</body>
</html>
如果想动态计算某些值的话, 提供一个粗暴简单的思路,直接用css覆盖。当然也可以使用window.requestAnimationFrame
来实现。
<script>
const style = document.createElement('style');
style.setAttribute('data-v', 'custom')
const duration = 10;
style.textContent = `
.container > ul > li:nth-child(2n) {
background-color: red;
}
.container > ul {
animation-duration: ${duration}s;
}
`;
document.body.appendChild(style);
</script>
# 2、轮播
轮播也是一样的原理, 无非就是改变内容的移动的值,也可以使用定位来实现, 按照设定的参数修改left的值即可了。
接下来实现一个原理性的轮播示例, 就使用js来达到一定的动态效果。 我自己瞎写的一个demo。明白原理即可
动画用的 transition 来实现的,也可以使用定时器或者requestAnimatiton来实现
# 2.1 transition
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>轮播</title>
</head>
<body>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
box-sizing: border-box;
}
.carouse-wrapper {
width: 600px;
height: 400px;
margin: 50px auto;
box-sizing: content-box;
border: 1px solid #ccc;
/* position: relative; */
background-color: #fff;
overflow: hidden;
}
.carousel-content {
height: 100%;
overflow: hidden;
}
.carousel-content__item {
font-size: 60px;
line-height: 400px;
text-align: center;
}
.carousel-content__item:nth-child(2n-1) {
background-color: #aaa;
}
</style>
<div class="carouse-wrapper" id="carouse_wrapper">
<!-- <ul class="carousel-content">
<li class="carousel-content__item"></li>
</ul> -->
</div>
<script>
function initCarousel(el, size, itemList = [], itemFactory = () => {}) {
if (typeof el === 'string') {
el = document.querySelector(`#${el}`);
}
if (!el.style.position || window.getComputedStyle(el).position === 'static') {
el.style.position = 'relative';
}
const { width, height } = size;
const carouselContentEl = document.createElement('ul');
carouselContentEl.classList.add('carousel-content');
carouselContentEl.style.width = width * (itemList.length + 1) + 'px';
carouselContentEl.style.left = '0px';
carouselContentEl.style.top = '0px';
carouselContentEl.style.position = 'absolute';
carouselContentEl.style.transition = 'left 1s linear';
const itemsFragment = document.createDocumentFragment();
const _itemList = [...itemList, itemList[0]];
_itemList.forEach((item, index) => {
const liEl = document.createElement('li');
liEl.classList.add('carousel-content__item');
liEl.setAttribute('data-index', index + '');
liEl.style.width = width + 'px';
liEl.style.height = height + 'px';
liEl.style.float = 'left';
if (itemFactory && typeof itemFactory === 'function') {
liEl.appendChild(itemFactory(item, index));
}
itemsFragment.appendChild(liEl);
});
carouselContentEl.append(itemsFragment);
el.appendChild(carouselContentEl);
if (itemList.length <= 1) {
return;
}
// return;
let _index = 0;
const __carouse_timer = setInterval(() => {
if (_index == itemList.length + 1) {
_index = 0;
carouselContentEl.style.transition = 'none';
carouselContentEl.style.left = '0px';
_index++;
setTimeout(() => {
carouselContentEl.style.transition = 'left 1s linear';
carouselContentEl.style.left = 0 - _index * width + 'px';
}, 0);
return;
}
carouselContentEl.style.left = 0 - _index * width + 'px';
_index++;
}, 2000);
return () => {
clearInterval(__carouse_timer);
};
}
const width = 600,
height = 400;
initCarousel(
'carouse_wrapper',
{
width,
height
},
[1, 2, 3],
data => {
const span = document.createElement('span');
span.textContent = data;
return span;
}
);
</script>
</body>
</html>
# 2.2 使用 requestAnimationFrame 来实现动画
简单写了一个动画函数, 60HZ的屏幕 step 函数会在一秒内被调用60次, 144hz的就会144次/分钟, 所以每一帧的动画距离, 也就是left的变化距离, 要简单根据帧的频率计算一下。比如需要移动 600px,要在2000ms内完成,屏幕是60hz,那么一帧大约就是16.7ms。
let interval = ((0 - 600) / 2000) * 16.7; 约等于-5
也就是每一帧需要移动5px。动画大约会在2s左右完成。
如果是144hz, let interval = ((0 - 600) / 2000) * 6.9; 约等于-2.08
也就是每一帧移动2.08px,动画会更加的细腻。
function doAnimate(el, from, to, duration) {
if (from === to) {
el.style.left = to + 'px';
return;
}
// let interval = to - from > 0 ? 10 : -10;
let interval = ((to - from) / duration) * keyFramesInterval;
console.debug('interval = ', interval);
let start, previousTimeStamp;
let done = false;
let _left = from;
el.style.left = _left + 'px';
console.time('完成动画');
function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
// console.log(_left);
_left += interval;
if (interval > 0) {
_left = Math.min(_left, to);
} else {
_left = Math.max(_left, to);
}
el.style.left = _left + 'px';
if (_left !== to) {
window.requestAnimationFrame(step);
} else {
// console.log(_left);
console.timeEnd('完成动画');
}
}
window.requestAnimationFrame(step);
}
完整示例如下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>轮播</title>
</head>
<body>
<style>
* {
padding: 0;
margin: 0;
list-style: none;
box-sizing: border-box;
}
.carouse-wrapper {
width: 600px;
height: 400px;
margin: 50px auto;
box-sizing: content-box;
border: 1px solid #ccc;
/* position: relative; */
background-color: #fff;
overflow: hidden;
}
.carousel-content {
height: 100%;
overflow: hidden;
}
.carousel-content__item {
font-size: 60px;
line-height: 400px;
text-align: center;
}
.carousel-content__item:nth-child(2n-1) {
background-color: #aaa;
}
</style>
<button onclick="cancel()">cancel</button>
<div class="carouse-wrapper" id="carouse_wrapper">
<!-- <ul class="carousel-content">
<li class="carousel-content__item"></li>
</ul> -->
</div>
<script>
function getKeyFramesInterval() {
return new Promise(resolve => {
let start;
function foo(timestamp) {
if (start === undefined) {
start = timestamp;
window.requestAnimationFrame(foo);
return;
}
if (start && timestamp) {
resolve(timestamp - start);
}
}
window.requestAnimationFrame(foo);
});
}
getKeyFramesInterval().then(res => {
console.log('getKeyFramesInterval', res);
});
let keyFramesInterval = 16.66666;
/*
假设要在2000ms内完成动画
*/
function doAnimate(el, from, to, duration) {
if (from === to) {
el.style.left = to + 'px';
return;
}
// let interval = to - from > 0 ? 10 : -10;
let interval = ((to - from) / duration) * keyFramesInterval;
console.debug('interval = ', interval);
let start, previousTimeStamp;
let done = false;
let _left = from;
el.style.left = _left + 'px';
console.time('完成动画');
function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
// console.log(_left);
_left += interval;
if (interval > 0) {
_left = Math.min(_left, to);
} else {
_left = Math.max(_left, to);
}
el.style.left = _left + 'px';
if (_left !== to) {
window.requestAnimationFrame(step);
} else {
// console.log(_left);
console.timeEnd('完成动画');
}
}
window.requestAnimationFrame(step);
}
function initCarousel(el, size, itemList = [], itemFactory = () => {}) {
if (typeof el === 'string') {
el = document.querySelector(`#${el}`);
}
if (!el.style.position || window.getComputedStyle(el).position === 'static') {
el.style.position = 'relative';
}
const { width, height } = size;
const carouselContentEl = document.createElement('ul');
carouselContentEl.classList.add('carousel-content');
carouselContentEl.style.width = width * (itemList.length + 1) + 'px';
carouselContentEl.style.left = '0px';
carouselContentEl.style.top = '0px';
carouselContentEl.style.position = 'absolute';
// carouselContentEl.style.transition = 'left 1s linear';
const itemsFragment = document.createDocumentFragment();
const _itemList = [...itemList, itemList[0]];
_itemList.forEach((item, index) => {
const liEl = document.createElement('li');
liEl.classList.add('carousel-content__item');
liEl.setAttribute('data-index', index + '');
liEl.style.width = width + 'px';
liEl.style.height = height + 'px';
liEl.style.float = 'left';
if (itemFactory && typeof itemFactory === 'function') {
liEl.appendChild(itemFactory(item, index));
}
itemsFragment.appendChild(liEl);
});
carouselContentEl.append(itemsFragment);
el.appendChild(carouselContentEl);
if (itemList.length <= 1) {
return;
}
// return;
let from = 0,
step = -600;
let to = from + step;
const __carouse_timer = setInterval(() => {
if (from === 0) {
carouselContentEl.style.left = '0px';
}
doAnimate(carouselContentEl, from, to, 2000);
if (to === itemList.length * step) {
from = 0;
to = from + step;
return;
}
from = to;
to = from + step;
}, 3000);
return () => {
clearInterval(__carouse_timer);
};
}
let _cancel;
getKeyFramesInterval().then(a => {
keyFramesInterval = a;
const width = 600,
height = 400;
_cancel = initCarousel(
'carouse_wrapper',
{
width,
height
},
[1, 2, 3, 4],
data => {
const span = document.createElement('span');
span.textContent = data;
return span;
}
);
});
function cancel() {
_cancel();
}
</script>
</body>
</html>
上面这些只是基础和原理,我也只是提供一个思路。更多效果和动画函数,可以使用https://swiperjs.com/ (opens new window)