基本思路
实现思路就是依据一般无缝轮播图的做法,将第一张图片复制插入轮播图队尾,以起到平滑过渡最后一张图至第一张图的作用,过程如下图所示,由1号向前滑动过渡至4号原理亦如此。
transform
选择transform的原因,在于它的性能优势。transform的属性计算在浏览器渲染管线的最后阶段即Composite阶段,如图所示,这意味着transform的属性计算不会影响页面的回流和重绘。它的原理是在Composite阶段通过原先元素的位图信息缓存进行线性映射来完成属性计算,在动画过程中不会改变原先的位图信息缓存,从而也就避免触发回流和重绘。
实现过程以及渲染的问题
主要代码
/**
* params: new_index 目标索引值
* return: 返回轮播图当前图片位置
*/
function update(new_index) {
if (new_index < 0) {
new_index = data.length - 1;
_move(-width * data.length, false);
_index = 0;
dataBox.offsetWidth; //强行刷新渲染队列
}
if (new_index > data.length) {
new_index = 1;
_move(0, false);
_index = 0;
dataBox.offsetWidth;
}
_move(-new_index * width, true);
_index = new_index;
return (_index % data.length);
}
function _move(distance, animation) {
dataBox.style.transition = animation ? 'transform .5s' : 'none';
dataBox.style.transform = `translateX(${distance}px)`;
}
复制代码
offsetWidth刷新渲染队列
之所以在由队尾的1号图片无动画切换至队首1号图片后,使用offsetWidth强制刷新渲染队列,是因为现在的浏览器会把一些dom的属性变更操作,加入到渲染队列中,最后统一刷新渲染队列进行一次回流,从而减少页面的回流次数。但是,这会使得像上面代码所示的transition transform
属性的两次更改在回流的过程中只保留了最后一次的值。不刷新和刷新效果分别如下图所示。刷新渲染队列不仅限于offsetWidth
,例如offsetTop,offsetLeft,offsetWidth,offsetHeight,scrollTop,scrollLeft,scrollWidth,scrollHeight,clientTop,clientLeft,clientWidth,clientHeight
等都可以触发。我也有看到利用requestAnimationFrame(()=>{requestAnimationFrame(callback)})
这样的方式去让callback内的样式变更,放在下一帧去回流。
完整代码
function Slide(dom, data) {
let app, width, height, dataBox, _index, timer, timeout;
function _create() {
if (dom == null || data == null || data.length < 2) {
throw new Error('create失败');
}
app = document.getElementById(dom);
app.style.cssText = 'overflow: hidden';
appStyle = getComputedStyle(app);
width = parseInt(appStyle.width);
height = parseInt(appStyle.height);
dataBox = document.createElement('div');
_index = 0;
dataBox.style.cssText = `
position: relative;
height: ${height}px;
width: ${(data.length + 1) * width}px;
`;
imgCSS = `
width: ${width}px;
height: ${height}px;
float: left;
`
data.forEach(img => {
let el = document.createElement('img');
el.src = img;
el.style.cssText = imgCSS;
dataBox.appendChild(el);
});
let el = document.createElement('img');
el.src = data[0];
el.style.cssText = imgCSS;
dataBox.appendChild(el);
app.appendChild(dataBox);
}
function _move(distance, animation) {
dataBox.style.transition = animation ? 'transform .5s' : 'none';
dataBox.style.transform = `translateX(${distance}px)`;
}
function _autoPlay() {
clearTimeout(timer);
timer = setTimeout(() => {
next();
_autoPlay();
}, timeout);
}
function update(new_index) {
if (new_index < 0) {
new_index = data.length - 1;
_move(-width * data.length, false);
_index = 0;
dataBox.offsetWidth;
}
if (new_index > data.length) {
new_index = 1;
_move(0, false);
_index = 0;
dataBox.offsetWidth;
}
_move(-new_index * width, true);
_index = new_index;
return (_index % data.length);
}
function next() {
return update(_index + 1);
}
function pre() {
return update(_index - 1);
}
function setTime(ms = 1000) {
let start = () => { _autoPlay() }
let end = () => { clearTimeout(timer) }
if (ms == -1) {
app.removeEventListener('mouseover', end);
app.removeEventListener('mouseleave', start);
end();
} else {
timeout = ms < 500 ? 1000 : ms;
app.addEventListener('mouseover', end);
app.addEventListener('mouseleave', start);
start();
}
}
_create();
return { next, pre, update, setTime }; //setTime输入-1表示关闭动画
}
复制代码
参考链接
从原理上理解,如何利用 CSS3 transition 打造无限轮播图