Skip to main content

手写Swiper

概述

知识点:

【css】transform、transition 【js】touchstart、touchmove、touchend

原理

拖动的时候改变 transfrom: translateX(x)来改变元素的位置

使用 transform 移动元素时会启用硬件加速渲染画面,性能更好

方式一:实时更新坐标

默认水平方向

/* eslint-disable consistent-this */
// 变量
const SPEED = 50;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
// 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
this.option = {
initialSlide: 0,
...option,
};
this.state = {
slideGrid: [], // 轮播数组的translate集合
initialTouch: {
clientX: 0,
clientY: 0,
},
distance: {
distanceX: 0,
distanceY: 0,
},
currentIndex: 0,
};
// 执行初始化
this.init();
}

init() {
this.eventsListen();
this.getData();
this.getAnimation();
}
// Event
eventsListen() {
const swiper = this;

swiper.el.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
swiper.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
swiper.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
}
// Getter
getFetch() {}
getData() {
this.getSlideGrid();
}
getNode() {}
getStyle() {}
getAnimation() {
const swiper = this;
const el = swiper.el;

el.style.transition = "all 0.5s ease";
}
// Method
touchStart(e) {
e.stopPropagation();
const swiper = this;
const { clientY, clientX } = e.touches[0];

swiper.state.initialTouch = { clientX, clientY };
}
// 计算移动距离
calculateDistance = (touch) => {
const { initialTouch } = this.state;

return {
distanceY: touch.clientY - initialTouch.clientY,
distanceX: touch.clientX - initialTouch.clientX,
};
};
touchMove(e) {
e.stopPropagation();
const swiper = this;
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

swiper.state.distance = { distanceX, distanceY };
}
touchEnd(e) {
e.stopPropagation();
const swiper = this;
const {
distance: { distanceX },
currentIndex,
} = swiper.state;

if (distanceX > 0) {
swiper.state.currentIndex = currentIndex - 1;
} else if (distanceX < 0) {
swiper.state.currentIndex = currentIndex + 1;
}

this.doTranslate(swiper.state.currentIndex);
}
// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push(slideChildNodes[i].offsetLeft);
}
swiper.state.slideGrid = arr;
}
// 执行动画
doTranslate(index) {
const swiper = this;
const el = swiper.el;
const { slideGrid } = swiper.state;

el.style.transform = `translate3d(-${slideGrid[index]}px, 0px,0px)`;
}
// Render
render() {}
}
export default Swiper;
const initSwiper = {
initialSlide: 0,
};

let swiper = new Swiper("swiper-wrapper", initSwiper);

<div className="swiper-wrapper">
<div>0</div>
<div>1</div>
<div>2</div>
</div>;

添加垂直方向

/* eslint-disable consistent-this */
// 变量
const distanceToRefresh = 50;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
// 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
this.option = {
direction: "horizontal", // 水平方向horizontal 垂直方向vertical
initialSlide: 0,
...option,
};
this.state = {
slideGrid: [], // 轮播数组的translate集合
initialTouch: {
clientX: 0,
clientY: 0,
},
distance: {
distanceX: 0,
distanceY: 0,
},
currentIndex: 0,
};
// 执行初始化
this.init();
}

init() {
const swiper = this;

this.eventsListen();
this.getData();
this.getAnimation();
}
// Event
eventsListen() {
const swiper = this;

swiper.el.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
swiper.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
swiper.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
}
// Getter
getFetch() {}
getData() {
this.getSlideGrid();
}
getNode() {}
getStyle() {}
getAnimation() {
const swiper = this;
const el = swiper.el;

el.style.transition = "all 0.5s ease";
}
// Method
touchStart(e) {
e.stopPropagation();
const swiper = this;
const { clientY, clientX } = e.touches[0];

swiper.state.initialTouch = { clientX, clientY };
}
// 计算移动距离
calculateDistance = (touch) => {
const { initialTouch } = this.state;

return {
distanceY: touch.clientY - initialTouch.clientY,
distanceX: touch.clientX - initialTouch.clientX,
};
};
touchMove(e) {
e.stopPropagation();
const swiper = this;
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

swiper.state.distance = { distanceX, distanceY };
}
touchEnd(e) {
e.stopPropagation();
const swiper = this;
const { direction } = swiper.option;
const {
distance: { distanceX, distanceY },
currentIndex,
slideGrid,
} = swiper.state;

if (direction === "horizontal") {
if (distanceX > 0) {
swiper.state.currentIndex = currentIndex - 1 < 0 ? 0 : currentIndex - 1;
} else if (distanceX < 0) {
swiper.state.currentIndex =
currentIndex + 1 < slideGrid.length
? currentIndex + 1
: slideGrid.length - 1;
}
}

if (direction === "vertical") {
if (distanceY > 0) {
swiper.state.currentIndex = currentIndex - 1 < 0 ? 0 : currentIndex - 1;
} else if (distanceY < 0) {
swiper.state.currentIndex =
currentIndex + 1 < slideGrid.length
? currentIndex + 1
: slideGrid.length - 1;
}
}

this.doTranslate(swiper.state.currentIndex);
}
// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push({
translateX: slideChildNodes[i].offsetLeft,
translatY: slideChildNodes[i].offsetTop,
});
}
swiper.state.slideGrid = arr;
}
// 执行动画
doTranslate(index) {
const swiper = this;
const {
el,
option: { direction },
state: { slideGrid },
} = swiper;

if (direction === "horizontal") {
const { translateX } = slideGrid[index];

el.style.transform = `translate3d(-${translateX}px, 0px,0px)`;
} else {
const { translatY } = slideGrid[index];

el.style.transform = `translate3d(0px,-${translatY}px,0px)`;
}
}
// Render
render() {}
}

export default Swiper;
const initSwiper = {
direction: "horizontal", // 水平方向horizontal 垂直方向vertical
initialSlide: 0,
};

let swiper = new Swiper("swiper-wrapper", initSwiper);

<div className="swiper-wrapper">
<div>0</div>
<div>1</div>
<div>2</div>
</div>;

拖拽 Slide

/* eslint-disable consistent-this */
// 变量
const distanceToRefresh = 30;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
// 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
this.option = {
direction: "horizontal", // 水平方向horizontal 垂直方向vertical
initialSlide: 0,
...option,
};
this.state = {
touchMoved: false, // 是否触摸
slideGrid: [], // 轮播数组的translate集合
initialTouch: {
lastX: 0,
lastY: 0,
},
distance: {
distanceX: 0,
distanceY: 0,
},
currentIndex: 0,
};
// 执行初始化
this.init();
}

init() {
const swiper = this;

this.eventsListen();
this.getData();
this.getAnimation();
}
// Event
eventsListen() {
const swiper = this;

swiper.el.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
swiper.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
swiper.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
}
// Getter
getFetch() {}
getData() {
this.getSlideGrid();
}
getNode() {}
getStyle() {}
getAnimation() {
const swiper = this;
const el = swiper.el;

// el.style.transition = 'all 0s';
}
// Method
touchStart(e) {
const swiper = this;
const { clientY, clientX } = e.touches[0];

// 设置初始位置
swiper.state.initialTouch = { lastX: clientX, lastY: clientY };
}
touchMove(e) {
const { clientY, clientX } = e.touches[0];
const swiper = this;
const { el } = swiper;

// 移动距离
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

// 正则提取translate
const { translateX, translateY } = this.regTranlate();

// 起始位置(获取最新位置)
let lastX = translateX;
let lastY = translateY;

// 计算位置
let resultX = distanceX + lastX;
let resultY = distanceY + lastY;

this.doTranslate(resultX, resultY);

// 更新起始位置
swiper.state.initialTouch = { lastX: clientX, lastY: clientY };
}
touchEnd() {
const swiper = this;
}
// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push({
translateX: slideChildNodes[i].offsetLeft,
translatY: slideChildNodes[i].offsetTop,
});
}

swiper.state.slideGrid = arr;
}
// 计算移动距离
calculateDistance = (touch) => {
const { initialTouch } = this.state;

return {
distanceY: touch.clientY - initialTouch.lastY,
distanceX: touch.clientX - initialTouch.lastX,
};
};
// 正则提取translate
RegTranlate() {
const swiper = this;
const { el } = swiper;
// eslint-disable-next-line no-useless-escape
let reg = /(\-|\+)?\d+(\.\d+)?px/g;
let str = el.style.transform;
let translate3d = str.match(reg);

return {
translateX: translate3d && translate3d[0] ? parseInt(translate3d[0]) : 0,
translateY: translate3d && translate3d[1] ? parseInt(translate3d[1]) : 0,
};
}
// 正则提取translate
regTranlate() {
const swiper = this;
const { el } = swiper;
// eslint-disable-next-line no-useless-escape
let reg = /(\-|\+)?\d+(\.\d+)?px/g;
let str = el.style.transform;
let translate3d = str.match(reg);

return {
translateX: translate3d && translate3d[0] ? parseInt(translate3d[0]) : 0,
translateY: translate3d && translate3d[1] ? parseInt(translate3d[1]) : 0,
};
}

// 执行动画
doTranslate(pixelsX, pixelsY) {
const swiper = this;
const { el } = swiper;

el.style.transform = `translate3d(${pixelsX}px,0px,0px)`;
// el.style.transform = `translate3d(0px,${pixelsY}px,0px)`;
}

// Render
render() {}
}

export default Swiper;

自动滑动

/* eslint-disable consistent-this */
// 变量
const distanceToRefresh = 30;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
// 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
this.option = {
direction: "horizontal", // 水平方向horizontal 垂直方向vertical
initialSlide: 0,
...option,
};
this.state = {
touchMoved: false, // 是否触摸
slideGrid: [], // 轮播数组的translate集合
initialTouch: {
lastX: 0,
lastY: 0,
},
distance: {
distanceX: 0,
distanceY: 0,
},
currentIndex: 0,
};
// 执行初始化
this.init();
}

init() {
const swiper = this;

this.eventsListen();
this.getData();
this.getAnimation();
}
// Event
eventsListen() {
const swiper = this;

swiper.el.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
swiper.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
swiper.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
swiper.el.addEventListener(
"transitionend",
this.transitionend.bind(swiper),
false
);
}
// Getter
getFetch() {}
getData() {
this.getSlideGrid();
}
getNode() {}
getStyle() {}
getAnimation() {
const swiper = this;
const el = swiper.el;
}
// Method
transitionend(e) {
const swiper = this;
const { el } = swiper;

el.style.transition = null;
}
touchStart(e) {
const swiper = this;
const { clientY, clientX } = e.touches[0];

// 设置初始位置
swiper.state.initialTouch = { lastX: clientX, lastY: clientY };
}
touchMove(e) {
const { clientY, clientX } = e.touches[0];
const swiper = this;
const { el } = swiper;

// 移动距离
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

// 正则提取translate
const { translateX, translateY } = this.regTranlate();

// 起始位置(获取最新位置)
let lastX = translateX;
let lastY = translateY;

// 计算位置
let resultX = distanceX + lastX;
let resultY = distanceY + lastY;

this.doTranslate(resultX, resultY);

// 更新起始位置
swiper.state.initialTouch = { lastX: clientX, lastY: clientY };
}
touchEnd() {
const swiper = this;
const { translateX, translateY } = this.regTranlate();

this.goToSlide(translateX);
}
// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push({
translateX: slideChildNodes[i].offsetLeft,
translatY: slideChildNodes[i].offsetTop,
});
}
console.log(arr);
swiper.state.slideGrid = arr;
}
// 计算移动距离
calculateDistance = (touch) => {
const { initialTouch } = this.state;

return {
distanceY: parseInt(touch.clientY - initialTouch.lastY),
distanceX: parseInt(touch.clientX - initialTouch.lastX),
};
};
// 正则提取translate
regTranlate() {
const swiper = this;
const { el } = swiper;
// eslint-disable-next-line no-useless-escape
let reg = /(\-|\+)?\d+(\.\d+)?px/g;
let str = el.style.transform;
let translate3d = str.match(reg);

return {
translateX: translate3d && translate3d[0] ? parseInt(translate3d[0]) : 0,
translateY: translate3d && translate3d[1] ? parseInt(translate3d[1]) : 0,
};
}
// 计算translate距离
boundTranslate(distance) {
const swiper = this;
const { el } = swiper;

this.doTranslate(distance);
}
// 执行动画
doTranslate(pixelsX, pixelsY) {
const swiper = this;
const { el } = swiper;

el.style.transform = `translate3d(${pixelsX}px,0px,0px)`;
// el.style.transform = `translate3d(0px,${pixelsY}px,0px)`;
}
goToSlide(translateX) {
const swiper = this;
const {
el,
option: { direction },
state: { slideGrid },
} = swiper;

// 设置边界
const leftBounde = translateX > 0;
const rightBounde = Math.abs(translateX) > el.scrollWidth - el.clientWidth; // 减去自身一个

if (leftBounde) {
el.style.transition = "all .3s";
el.style.transform = "translate3d(0px,0px,0px)";
}
if (rightBounde) {
el.style.transition = "all .3s";
el.style.transform = `translate3d(-${
el.scrollWidth - el.clientWidth
}px,0px,0px)`;
}
}
// Render
render() {}
}

export default Swiper;

自动滚动下一页

/* eslint-disable consistent-this */
// 变量:最大拉伸距离
const distanceMaximum = 30;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
this.option = {
direction: "horizontal", // 水平方向horizontal/垂直方向vertical
initialSlide: 0, // 初始页
...option, // 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
};
this.state = {
touchMoved: false, // 是否移动结束
slideGrid: [], // 轮播数组的translate集合
initialTouch: {
// 判断方向
initX: 0,
initY: 0,
},
lastTouch: {
// 记录上次
lastX: 0,
lastY: 0,
},
distance: {
// 移动距离
distanceX: 0,
distanceY: 0,
},
currentIndex: 0, // 当前页面
};
// 执行初始化
this.init();
}

init() {
const swiper = this;

this.eventsListen();
this.getData();
this.getAnimation();
}
// Event
eventsListen() {
const swiper = this;

swiper.el.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
swiper.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
swiper.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
swiper.el.addEventListener(
"transitionend",
this.transitionend.bind(swiper),
false
);
}
// Getter
getFetch() {}
getData() {
this.getSlideGrid();
}
getNode() {}
getStyle() {}
getAnimation() {
const swiper = this;
const el = swiper.el;
}
// Method
transitionend(e) {
const swiper = this;
const { el } = swiper;

el.style.transition = null;
}
touchStart(e) {
const swiper = this;
const { clientY, clientX } = e.touches[0];

// 设置初始位置
swiper.state.initialTouch = { initX: clientX, initY: clientY };
// 设置上一次位置
swiper.state.lastTouch = { lastX: clientX, lastY: clientY };
}
touchMove(e) {
const { clientY, clientX } = e.touches[0];
const swiper = this;
const { el } = swiper;

// 移动距离
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

// 正则提取translate
const { translateX, translateY } = this.regTranlate();

// 起始位置(获取最新位置)
let lastX = translateX;
let lastY = translateY;

// 计算位置
let resultX = distanceX + lastX;
let resultY = distanceY + lastY;

this.doTranslate(resultX, resultY);

// 更新起始位置
swiper.state.lastTouch = { lastX: clientX, lastY: clientY };
}
touchEnd() {
const swiper = this;

this.setBound();
this.goToSlide();
}
// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push({
translateX: slideChildNodes[i].offsetLeft,
translatY: slideChildNodes[i].offsetTop,
});
}

swiper.state.slideGrid = arr;
}
// 计算移动距离
calculateDistance = (touch) => {
const { lastTouch } = this.state;

return {
distanceY: parseInt(touch.clientY - lastTouch.lastY),
distanceX: parseInt(touch.clientX - lastTouch.lastX),
};
};
// 正则提取translate
regTranlate() {
const swiper = this;
const { el } = swiper;
// eslint-disable-next-line no-useless-escape
let reg = /(\-|\+)?\d+(\.\d+)?px/g;
let str = el.style.transform;
let translate3d = str.match(reg);

return {
translateX: translate3d && translate3d[0] ? parseInt(translate3d[0]) : 0,
translateY: translate3d && translate3d[1] ? parseInt(translate3d[1]) : 0,
};
}
// 执行动画
doTranslate(pixelsX, pixelsY) {
const swiper = this;
const { el } = swiper;

el.style.transform = `translate3d(${pixelsX}px,0px,0px)`;
// el.style.transform = `translate3d(0px,${pixelsY}px,0px)`;
}
setBound() {
const swiper = this;
const { el } = swiper;

const { translateX, translateY } = this.regTranlate();

// 设置边界
const leftBounde = translateX > 0;
const rightBounde = Math.abs(translateX) > el.scrollWidth - el.clientWidth; // 减去自身一个

if (leftBounde) {
el.style.transition = "all .5s";
el.style.transform = "translate3d(0px,0px,0px)";
}
if (rightBounde) {
el.style.transition = "all .5s";
el.style.transform = `translate3d(-${
el.scrollWidth - el.clientWidth
}px,0px,0px)`;
}
}
goToSlide() {
const swiper = this;
let {
el,
state: {
slideGrid,
initialTouch: { initX },
lastTouch: { lastX },
currentIndex,
},
} = swiper;

// 判断方向
if (lastX > initX && currentIndex > 0) {
currentIndex -= 1;
} else if (lastX < initX && currentIndex < slideGrid.length - 1) {
currentIndex += 1;
}

const { translateX } = slideGrid[currentIndex];

el.style.transition = "all .5s";
el.style.transform = `translate3d(-${translateX}px,0px,0px)`;
swiper.state.currentIndex = currentIndex;
}
// Render
render() {}
}

export default Swiper;

重构

/* eslint-disable consistent-this */
// 最大拉伸阈值
const maxStretch = 100;
// 滑动切换的阈值
const maxSlide = 50;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
this.option = {
direction: "horizontal", // 水平方向horizontal/垂直方向vertical
initialSlide: 0, // 初始页
...option, // 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
};
this.state = {
slideGrid: [], // 轮播数组的translate集合
initTouch: {
initX: 0,
initY: 0,
},
lastTouch: {
// 实时更新地址
lastX: 0,
lastY: 0,
},
distance: {
// 移动距离
distanceX: 0,
distanceY: 0,
},
currentIndex: 0, // 当前页面
};
// 执行初始化
this.init();
}
init() {
const swiper = this;

this.getNode();
this.getData();
this.eventsListen();
this.setAnimation();
this.mount();
}

// Event
eventsListen() {
const swiper = this;

this.el.addEventListener("touchstart", this.touchStart.bind(swiper), false);
this.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
this.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
this.el.addEventListener(
"transitionend",
this.transitionend.bind(swiper),
false
);
}

// Getter
getData() {
this.getSlideGrid();
}
getNode() {}
// Setter
setAnimation() {}

// Method
touchStart(e) {
e.preventDefault();
const swiper = this;
const { clientY, clientX } = e.touches[0];

// 设置初始位置(用作比较)
this.state.initTouch = { initX: clientX, initY: clientY };
// 设置上一次位置
this.state.lastTouch = { lastX: clientX, lastY: clientY };
}
touchMove(e) {
e.preventDefault();
const { clientX, clientY } = e.touches[0];
const firstX = clientX;
const swiper = this;
const {
state: {
currentIndex,
initTouch: { initX },
},
} = this;
// 移动距离
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

// 获取最新位置:正则提取translate
const { lastX, lastY } = this.regTranlate();

// 计算位置
let resultX = distanceX + lastX;
let resultY = distanceY + lastY;

// 最大限度
if (Math.abs(clientX - initX) > maxStretch) {
console.log("超出最大限度");

return;
}

// 移动
this.doTranslate(resultX, resultY);

// 更新起始位置,下一次移动就从当前位置计算
swiper.state.lastTouch = { lastX: clientX, lastY: clientY };
}
touchEnd(e) {
e.preventDefault();
this.goToSlide();
}
transitionend(e) {
const swiper = this;
const { el } = this;

// 清除过渡动画
el.style.transition = null;
}

// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push({
translateX: slideChildNodes[i].offsetLeft,
translatY: slideChildNodes[i].offsetTop,
});
}

this.state.slideGrid = arr;
}
// 计算移动距离
calculateDistance = (touch) => {
const { lastTouch } = this.state;

const distanceX = touch.clientX - lastTouch.lastX;
const distanceY = touch.clientY - lastTouch.lastY;

return {
distanceX: parseInt(distanceX),
distanceY: parseInt(distanceY),
};
};
// 正则提取translate
regTranlate() {
const swiper = this;
const { el } = this;
// eslint-disable-next-line no-useless-escape
let reg = /(\-|\+)?\d+(\.\d+)?px/g;
let str = el.style.transform;
let translate = str.match(reg);

return {
lastX: translate && translate[0] ? parseInt(translate[0]) : 0,
lastY: translate && translate[1] ? parseInt(translate[1]) : 0,
};
}
// 进行移动
doTranslate(pixelsX, pixelsY) {
const swiper = this;
const { el } = this;

const translate = `translate(${pixelsX}px,0px)`;

el.style.cssText = `
-moz-transform:${translate};
-ms-transform:${translate};
-o-transform:${translate};
-webkit-transform:${translate};
transform:${translate};
`;
}
// 执行page动画
goToSlide() {
const swiper = this;
let {
el,
state: {
slideGrid,
initTouch: { initX },
lastTouch: { lastX },
currentIndex,
},
} = this;

// 最大slide识别距离
if (Math.abs(lastX - initX) < maxSlide) {
const { translateX } = slideGrid[currentIndex];

const translate = `translate(-${translateX}px,0px)`;

el.style.cssText = `
transition:transform 600ms;
-moz-transform:${translate};
-ms-transform:${translate};
-o-transform:${translate};
-webkit-transform:${translate};
transform:${translate};
`;

return;
}

// 判断方向
if (lastX > initX && currentIndex > 0) {
currentIndex -= 1;
} else if (lastX < initX && currentIndex < slideGrid.length - 1) {
currentIndex += 1;
}

const { translateX } = slideGrid[currentIndex];

const translate = `translate(-${translateX}px,0px)`;

el.style.cssText = `
transition:transform 300ms;
-moz-transform:${translate};
-ms-transform:${translate};
-o-transform:${translate};
-webkit-transform:${translate};
transform:${translate};
`;

// el.style.transition = 'transform 600ms';
// el.style.transform = `translate(-${translateX}px,0px)`;

this.state.currentIndex = currentIndex;
}
// Mount
mount() {
window.Swiper = this;
}
}

export default Swiper;

方式二:实时更新距离

/* eslint-disable consistent-this */

class Swiper {
constructor(element) {
this.swiperWrapper = document.getElementsByClassName(element)[0];
this.swiperContainer = this.swiperWrapper.childNodes[0];

this.state = {
start: 0, // 触摸起始点
moveDistance: 0, // 滑动距离
isMoved: false, // 标记是否移动过
lastMove: 0,
moveX: 0,
currntItemIndex: 0, // 当前是第几个item
};
this.init();
}
init() {
this.eventsListen();
this.setStyle();
this.createIndicator();
this.mount();
}

// Event
eventsListen() {
const swiper = this;

this.swiperContainer.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
this.swiperContainer.addEventListener(
"touchmove",
this.touchMove.bind(swiper),
false
);
this.swiperContainer.addEventListener(
"touchend",
this.touchEnd.bind(swiper),
false
);
}
// Getter
getData() {}
getNode() {}
// Setter
setStyle() {
this.swiperWrapper = document.getElementsByClassName("swiper-wrapper")[0]; // swiper容器
const width = window.innerWidth;

this.swiperWrapper.style.width = `${width}px`;
}

// Method
touchStart(e) {
const swiper = this;
const {
state: { moveX },
} = this;
const { clientX } = e.touches[0];

this.state.start = clientX;
// 拿到上次滑动的距离(每次固定)
this.state.lastMove = moveX;
}
touchMove(e) {
const swiper = this;
const {
state: { start, isMoved, lastMove },
} = this;
const { clientX } = e.touches[0];

// 计算移动距离
let distanceX = clientX - start;

// 重新设置滑动的距离
let resultX = isMoved ? lastMove + distanceX : distanceX;

this.swiperContainer.style = `transform: translateX(${resultX}px)`;

// 更新移动位置
this.state.moveX = resultX;

// 标记已经滑动
this.state.isMoved = true;

// 更新移动距离
this.state.moveDistance = distanceX;
}
touchEnd(e) {
const swiper = this;
const {
state: { moveX, moveDistance, currntItemIndex },
} = this;

let result = moveX;
let rightBound = -`${
this.swiperContainer.scrollWidth - this.swiperContainer.clientWidth
}`;
let childLength = this.swiperContainer.childNodes.length - 1;

// 处理左边界
if (moveX > 0) {
result = 0;
}
// 处理右边界
if (moveX < rightBound) {
result = rightBound;
}

// 处理中间swiper-item的边界
if (moveDistance > 0) {
// 从左往右滑动(→)
if (
moveX > -(this.swiperContainer.clientWidth * currntItemIndex) + 50 &&
currntItemIndex > 0
) {
// 滑到上一个swiper-item
result = -(this.swiperContainer.clientWidth * (currntItemIndex - 1));
} else {
// 停留在当前swiper-item
result = -(this.swiperContainer.clientWidth * currntItemIndex);
}
} else if (moveDistance < 0) {
// 从右往左滑动(←)
if (
moveX < -(this.swiperContainer.clientWidth * currntItemIndex) - 50 &&
currntItemIndex < childLength
) {
// 滑到下一个swiper-item
result = -(this.swiperContainer.clientWidth * (currntItemIndex + 1));
} else {
// 停留在当前swiper-item
result = -(this.swiperContainer.clientWidth * currntItemIndex);
}
}

// 更新样式
this.swiperContainer.style = `transform: translateX(${result}px);transition: transform 600ms`;

// 计算当前index
let index = Math.abs(Math.round(result / this.swiperContainer.clientWidth));

this.state.currntItemIndex = index;

// 更新指示点
let dots = document.getElementsByClassName("dot");

for (let i = 0; i < dots.length; i++) {
dots[i].setAttribute("class", `dot ${index === i ? "dot-active" : ""}`);
}

// 更新移动
this.state.moveX = result;

// 滑动距离恢复为0
this.state.moveDistance = 0;
}

// 指示器
createIndicator() {
const swiper = this;
const {
state: { currntItemIndex },
} = this;
let dotWrap = document.createElement("div");
let dot = document.createElement("span");

// clas类名
dotWrap.setAttribute("class", "indicator-wrapper");

// // style样式
// dotWrap.style.cssText = `
// width:100px;
// height:100px;
// `;

const length = this.swiperContainer.childNodes.length;

for (let i = 0; i < length; i++) {
dot.setAttribute(
"class",
`dot ${currntItemIndex === i ? "dot-active" : ""}`
);
dotWrap.appendChild(dot.cloneNode(true));
}

this.swiperWrapper.appendChild(dotWrap.cloneNode(true));
}
// Mount
mount() {
window.Swiper = this;
}
}

export default Swiper;

最新版(translate)

/* eslint-disable consistent-this */
// 最大拉伸阈值
const maxStretch = 100;
// 滑动切换的阈值
const maxSlide = 50;

class Swiper {
constructor(element, option) {
this.el = element && document.getElementsByClassName(element)[0];
this.option = {
direction: "horizontal", // 水平方向horizontal/垂直方向vertical
initialSlide: 0, // 初始页
...option, // 默认参数:检查是否传了参数,如果传了参数将参数和默认参数合并
};
this.state = {
slideGrid: [], // 轮播数组的translate集合
initTouch: {
initX: 0,
initY: 0,
},
lastTouch: {
// 实时更新地址
lastX: 0,
lastY: 0,
},
distance: {
// 移动距离
distanceX: 0,
distanceY: 0,
},
currentIndex: 0, // 当前页面
};
// 执行初始化
this.init();
}
init() {
const swiper = this;

this.getNode();
this.getData();
this.eventsListen();
this.setAnimation();
this.mount();
}

// Event
eventsListen() {
const swiper = this;

this.el.addEventListener("touchstart", this.touchStart.bind(swiper), false);
this.el.addEventListener("touchmove", this.touchMove.bind(swiper), false);
this.el.addEventListener("touchend", this.touchEnd.bind(swiper), false);
this.el.addEventListener(
"transitionend",
this.transitionend.bind(swiper),
false
);
}

// Getter
getData() {
this.getSlideGrid();
}
getNode() {}
// Setter
setAnimation() {}

// Method
touchStart(e) {
e.preventDefault();
const swiper = this;
const { clientY, clientX } = e.touches[0];

// 设置初始位置(用作比较)
this.state.initTouch = { initX: clientX, initY: clientY };
// 设置上一次位置
this.state.lastTouch = { lastX: clientX, lastY: clientY };
}
touchMove(e) {
e.preventDefault();
const { clientX, clientY } = e.touches[0];
const firstX = clientX;
const swiper = this;
const {
state: {
currentIndex,
initTouch: { initX },
},
} = this;
// 移动距离
const { distanceX, distanceY } = this.calculateDistance(e.touches[0]);

// 获取最新位置:正则提取translate
const { lastX, lastY } = this.regTranlate();

// 计算位置
let resultX = distanceX + lastX;
let resultY = distanceY + lastY;

// 最大限度
if (Math.abs(clientX - initX) > maxStretch) {
console.log("超出最大限度");

return;
}

// 移动
this.doTranslate(resultX, resultY);

// 更新起始位置,下一次移动就从当前位置计算
swiper.state.lastTouch = { lastX: clientX, lastY: clientY };
}
touchEnd(e) {
e.preventDefault();
this.goToSlide();
}
transitionend(e) {
const swiper = this;
const { el } = this;

// 清除过渡动画
el.style.transition = null;
}

// 获取所有轮播的translate值 并保存到数组 所有的轮播都是通过这个数组来实现的
getSlideGrid() {
const swiper = this;

const slideChildNodes = this.el.childNodes;
const arr = [];

for (let i = 0; i < slideChildNodes.length; i++) {
arr.push({
translateX: slideChildNodes[i].offsetLeft,
translatY: slideChildNodes[i].offsetTop,
});
}

this.state.slideGrid = arr;
}
// 计算移动距离
calculateDistance = (touch) => {
const { lastTouch } = this.state;

const distanceX = touch.clientX - lastTouch.lastX;
const distanceY = touch.clientY - lastTouch.lastY;

return {
distanceX: parseInt(distanceX),
distanceY: parseInt(distanceY),
};
};
// 正则提取translate
regTranlate() {
const swiper = this;
const { el } = this;
// eslint-disable-next-line no-useless-escape
let reg = /(\-|\+)?\d+(\.\d+)?px/g;
let str = el.style.transform;
let translate = str.match(reg);

return {
lastX: translate && translate[0] ? parseInt(translate[0]) : 0,
lastY: translate && translate[1] ? parseInt(translate[1]) : 0,
};
}
// 进行移动
doTranslate(pixelsX, pixelsY) {
const swiper = this;
const { el } = this;

const translate = `translate(${pixelsX}px,0px)`;

el.style.cssText = `
-moz-transform:${translate};
-ms-transform:${translate};
-o-transform:${translate};
-webkit-transform:${translate};
transform:${translate};
`;
}
// 执行page动画
goToSlide() {
const swiper = this;
let {
el,
state: {
slideGrid,
initTouch: { initX },
lastTouch: { lastX },
currentIndex,
},
} = this;

// 最大slide识别距离
if (Math.abs(lastX - initX) < maxSlide) {
const { translateX } = slideGrid[currentIndex];

const translate = `translate(-${translateX}px,0px)`;

el.style.cssText = `
transition:transform 600ms;
-moz-transform:${translate};
-ms-transform:${translate};
-o-transform:${translate};
-webkit-transform:${translate};
transform:${translate};
`;

return;
}

// 判断方向
if (lastX > initX && currentIndex > 0) {
currentIndex -= 1;
} else if (lastX < initX && currentIndex < slideGrid.length - 1) {
currentIndex += 1;
}

const { translateX } = slideGrid[currentIndex];

const translate = `translate(-${translateX}px,0px)`;

el.style.cssText = `
transition:transform 600ms;
-moz-transform:${translate};
-ms-transform:${translate};
-o-transform:${translate};
-webkit-transform:${translate};
transform:${translate};
`;

// el.style.transition = 'transform 600ms';
// el.style.transform = `translate(-${translateX}px,0px)`;

this.state.currentIndex = currentIndex;
}
// Mount
mount() {
window.Swiper = this;
}
}

export default Swiper;
import Swiper from "./Swiper";

const Layout = ({ content }) => {
useEffect(() => {
const initSwiper = {
direction: "vertical", // 水平方向horizontal 垂直方向vertical
initialSlide: 0,
};

new Swiper("swiper-container", initSwiper);
}, []);

return (
<div className="swiper-wrapper">
<div className="swiper-container">
<div className={`${styles.page0} ${styles.pageSlide} swiper-silde`}>
0
</div>
<div className={`${styles.page1} ${styles.pageSlide} swiper-silde`}>
1
</div>
<div className={`${styles.page2} ${styles.pageSlide} swiper-silde`}>
2
</div>
</div>
</div>
);
};
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
}
:global(.swiper-wrapper) {
margin: 0 auto;
overflow: hidden;
border: indianred 1px solid;
position: relative;
}
:global(.swiper-container) {
display: flex;
}

.pageSlide {
flex-shrink: 0; // 禁止压缩
width: 100%;
height: 50vh;
color: #fff;
}
.page0 {
background: grey;
}
.page1 {
background: green;
}
.page2 {
background: blue;
}

最新版(left)

/* eslint-disable consistent-this */

class Swiper {
constructor(element) {
this.swiperWrapper = document.getElementsByClassName(element)[0];
this.swiperContainer = this.swiperWrapper.childNodes[0];

this.state = {
start: 0, // 触摸起始点
moveDistance: 0, // 滑动距离
isMoved: false, // 标记是否移动过
lastMove: 0,
moveX: 0,
loop: true, // 是否loop
currntItemIndex: 0, // 当前Index
};
this.init();
}
init() {
if (this.state.loop) {
this.loop();
}

this.eventsListen();
this.setStyle();
// this.createIndicator();
this.mount();
}

// Event
eventsListen() {
const swiper = this;

this.swiperContainer.addEventListener(
"touchstart",
this.touchStart.bind(swiper),
false
);
this.swiperContainer.addEventListener(
"touchmove",
this.touchMove.bind(swiper),
false
);
this.swiperContainer.addEventListener(
"touchend",
this.touchEnd.bind(swiper),
false
);
this.swiperContainer.addEventListener(
"transitionend",
this.transitionend.bind(swiper),
false
);
}
// Getter
getData() {}
getNode() {}
// Setter
setStyle() {
this.swiperWrapper = document.getElementsByClassName("swiper-wrapper")[0]; // swiper容器
const width = window.innerWidth;

this.swiperWrapper.style.width = `${width}px`;
}

// Method
touchStart(e) {
const swiper = this;
const {
state: { moveX },
} = this;
const { clientX } = e.touches[0];

this.state.start = clientX;
// 拿到上次滑动的距离(每次固定)
this.state.lastMove = moveX;
}
touchMove(e) {
const swiper = this;
const {
state: { start, isMoved, lastMove },
} = this;
const { clientX } = e.touches[0];

// 计算移动距离
let distanceX = clientX - start;

// 重新设置滑动的距离
let resultX = isMoved ? lastMove + distanceX : distanceX;

// this.swiperContainer.style = `transform: translateX(${resultX}px)`;
this.swiperContainer.style.transitionDuration = "300ms";
this.swiperContainer.style.left = `${resultX}px`;

// 更新移动位置
this.state.moveX = resultX;

// 标记已经滑动
this.state.isMoved = true;

// 更新移动距离
this.state.moveDistance = distanceX;
}
touchEnd(e) {
const swiper = this;
const {
state: { moveX, moveDistance, loop, currntItemIndex },
} = this;

let result = moveX;
let rightBound = -`${
this.swiperContainer.scrollWidth - this.swiperContainer.clientWidth
}`;
let childLength = this.swiperContainer.childNodes.length;

// 处理左边界
if (moveX > 0) {
result = 0;
}
// 处理右边界
if (moveX < rightBound) {
result = rightBound;
}

// 处理中间swiper-item的边界
if (moveDistance > 0) {
// 从左往右滑动(→)
if (
moveX > -(this.swiperContainer.clientWidth * currntItemIndex) + 50 &&
currntItemIndex > 0
) {
// 滑到上一个swiper-item
result = -(this.swiperContainer.clientWidth * (currntItemIndex - 1));
} else {
// 停留在当前swiper-item
result = -(this.swiperContainer.clientWidth * currntItemIndex);
}
} else if (moveDistance < 0) {
// 从右往左滑动(←)
if (
moveX < -(this.swiperContainer.clientWidth * currntItemIndex) - 50 &&
currntItemIndex < childLength - 1
) {
// 滑到下一个swiper-item
result = -(this.swiperContainer.clientWidth * (currntItemIndex + 1));
} else {
// 停留在当前swiper-item
result = -(this.swiperContainer.clientWidth * currntItemIndex);
}
}

// 计算当前index
let index = Math.abs(Math.round(result / this.swiperContainer.clientWidth));

if (loop) {
if (index === 0) {
// 关键点 如果当前在 4fake 的位置

setTimeout(() => {
// 去掉动画
this.swiperContainer.style.transitionDuration = "0ms";

// 默默的右移board 至 4
this.swiperContainer.style.left = `${-1125}px`;
}, 300);

index = 3;
}

// 进入最后一项(同第一项)
if (index === childLength - 1) {
// 关键点 如果当前在 4fake 的位置

setTimeout(() => {
// 去掉动画
this.swiperContainer.style.transitionDuration = "0ms";

// 默默的右移board 至 4
this.swiperContainer.style.left = `${-375}px`;
}, 300);

index = 1;
}
}

this.state.currntItemIndex = index;
// 移动
// this.swiperContainer.style = `transform: translateX(${result}px);transition: transform 600ms`;

this.swiperContainer.style.transitionDuration = "300ms";
this.swiperContainer.style.left = `${result}px`;

// 更新指示点
let dots = document.getElementsByClassName("dot");

for (let i = 0; i < dots.length; i++) {
dots[i].setAttribute("class", `dot ${index === i ? "dot-active" : ""}`);
}

// 更新移动
this.state.moveX = result;

// 滑动距离恢复为0
this.state.moveDistance = 0;
}
// 指示器
createIndicator() {
const swiper = this;
const {
state: { currntItemIndex },
} = this;
let dotWrap = document.createElement("div");
let dot = document.createElement("span");

// clas类名
dotWrap.setAttribute("class", "indicator-wrapper");

const childNodes =
this.swiperContainer.querySelectorAll("div.swiper-slide");

for (let i = 0; i < childNodes.length; i++) {
dot.setAttribute(
"class",
`dot ${currntItemIndex === i ? "dot-active" : ""}`
);
dotWrap.appendChild(dot.cloneNode(true));
}
const dotItems = dotWrap.childNodes;

dotItems[0].style.display = "none";
dotItems[4].style.display = "none";

this.swiperWrapper.appendChild(dotWrap.cloneNode(true));
}
loop() {
const swiper = this;
const {
state: { currntItemIndex, moveX, lastMove },
} = this;
const swiperContainerChildNodes = this.swiperContainer.childNodes;

// 前后复制
this.swiperContainer.appendChild(
swiperContainerChildNodes[0].cloneNode(true)
);
this.swiperContainer.insertBefore(
swiperContainerChildNodes[2].cloneNode(true),
swiperContainerChildNodes[0]
);

// 定位到第一项
this.state.currntItemIndex = currntItemIndex + 1;
const result = -(this.swiperContainer.clientWidth * (currntItemIndex + 1));

// 更新样式
// this.swiperContainer.style.transform = `translateX(${result}px)`;

this.swiperContainer.style.left = `${result}px`;

this.state.moveX = result;
}
transitionend(e) {
const swiper = this;
const {
state: { loop, currntItemIndex, moveX },
} = this;
let childLength = this.swiperContainer.childNodes.length;
}
// Mount
mount() {
window.Swiper = this;
}
}

export default Swiper;
import Swiper from "./Swiper";

const Layout = ({ content }) => {
useEffect(() => {
new Swiper2("swiper-wrapper");
}, []);

return (
<div className="swiper-wrapper">
<div className="swiper-container">
<div className="swiper-slide">1</div>
<div className="swiper-slide bg-color-2">2</div>
<div className="swiper-slide">3</div>
</div>
</div>
);
};
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
/* border: 1px solid red; */
overflow: hidden;
}
:global(.swiper-wrapper) {
border: 1px solid blue;
margin: 0 auto;
overflow: hidden;
height: 50vh;
}
:global(.swiper-container) {
position: absolute;
width: 100%;
display: flex;
}
:global(.swiper-slide) {
flex: 1 0 auto;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
color: #fff;
background-color: grey;
font-size: 60px;
font-weight: bold;
}
:global(.bg-color-2) {
background: green;
}

:global(.indicator-wrapper) {
position: absolute;
left: 0;
bottom: 20px;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
}

:global(.dot) {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
margin: 0 4px;
}

:global(.dot-active) {
background-color: rgba(255, 255, 255, 1);
}

参考