工作原因,好久沒更新博客了,今天來個用原生js寫輪播圖, 上代碼
class Banner {
constructor(props = {
}) {
if (!(props?.root instanceof HTMLElement)) {
throw "root是必選dom節點";
}
// 狀態
this.state = {
};
// 參數
this.props = {
type: "move",
height: 600,
width: 1000,
imgs: [],
...props,
};
if (props.type === "move") {
this.imgIndex = props.startIndex ? props.startIndex + 1 : 1;
} else {
this.imgIndex = props.startIndex || 0;
}
this.autoTimer; //自動輪播計時器
this.animateTimer; //動畫計時器
this.init();
}
// 修改狀態
setState = (state = {
}) => {
this.state = {
...this.state,
...state,
};
};
// 初始化
init = () => {
this.render();
this.animate();
this.autoMove();
this.onChange();
};
// 渲染輪播圖
render = () => {
const {
root,
height,
width,
type,
imgs = [],
showButton = true,
showCheck = true,
} = this.props;
setStyle(root, {
height,
width,
position: "relative",
});
root.className += " banner";
const box = document.createElement("div");
const imgBox = document.createElement("ul");
const iconBox = document.createElement("ul");
imgs.forEach((item) => {
this.setImgNode(imgBox, item);
const icon = document.createElement("li");
iconBox.appendChild(icon);
});
// 滾動需要前後各插入圖片
if (type === "move") {
this.setImgNode(imgBox, imgs[0]);
this.setImgNode(imgBox, imgBox.children[0], imgs[imgs.length - 1]);
} else {
setStyle(imgBox.children[this.imgIndex], {
zIndex: 90,
opacity: 1,
});
}
setStyle(imgBox, {
width: type === "move" ? (imgs.length + 2) * width : width,
height,
});
addClass(
box,
type === "move" ? "banner-img-box-move" : "banner-img-box-transparency"
);
iconBox.className = "banner-icon-box";
box.appendChild(imgBox);
root.appendChild(box);
showCheck && root.appendChild(iconBox);
showButton && this.renderButton(root);
this.setState({
box,
imgBox,
iconBox,
});
};
// 渲染按鈕
renderButton = (node) => {
const rightBtn = document.createElement("button");
const leftBtn = document.createElement("button");
rightBtn.innerHTML = ">";
leftBtn.innerHTML = "<";
rightBtn.className = "btn banner-right-btn";
leftBtn.className = "btn banner-left-btn";
node.appendChild(rightBtn);
node.appendChild(leftBtn);
this.setState({
rightBtn,
leftBtn,
});
};
// 設置圖片函數
setImgNode(node) {
const {
height, width, type } = this.props;
const li = document.createElement("li");
li.innerHTML = `<img class="banner-img" src="${
arguments.length > 2 ? arguments[2] : arguments[1]
}" />`;
setStyle(li, {
height,
width,
opacity: type === "move" ? null : 0,
});
node.insertBefore(li, arguments.length > 2 ? arguments[1] || null : null);
}
//自動輪播
autoMove = () => {
const {
time = 3000 } = this.props;
this.autoTimer = setInterval(() => {
this.imgIndex++;
this.animate();
}, time);
};
//動畫
animate = () => {
const {
type } = this.props;
const {
iconBox } = this.state;
//dom集合轉數組
Array.from(iconBox.children).forEach((node, index) => {
if (node.className?.indexOf("active") > -1) {
removeClassName(node, "active");
}
if (this.imgIndex === (type === "move" ? index + 1 : index)) {
addClass(node, "active");
}
});
if (type === "move") {
this.move();
} else {
this.transparency();
}
};
//滾動輪播
move = () => {
const {
iconBox, box } = this.state;
const {
imgs, width } = this.props;
// 下標等於圖片長度時需要重置到第一個小圓點
if (this.imgIndex === imgs.length + 1) {
addClass(iconBox.children[0], "active");
}
// 圖片超過長度拉回第二張圖片
if (this.imgIndex === imgs.length + 2) {
this.imgIndex = 2;
box.scrollLeft = width;
addClass(iconBox.children[1], "active");
}
if (this.imgIndex === 0) {
addClass(iconBox.children[imgs.length - 1], "active");
}
//下標小於0時返回最後二張圖片的下標
if (this.imgIndex < 0) {
this.imgIndex = imgs.length - 1;
box.scrollLeft = width * imgs.length;
addClass(iconBox.children[imgs.length - 2], "active");
}
//滾動條移動
this.scrollMove();
};
//淡入淡出
transparency = () => {
const {
imgBox, iconBox } = this.state;
const {
imgs } = this.props;
if (this.imgIndex === imgs.length) {
//下標到最後一張圖片時變回第一張圖片
this.imgIndex = 0;
addClass(iconBox.children[0], "active");
}
if (this.imgIndex < 0) {
this.imgIndex = imgs.length - 1;
addClass(iconBox.children[imgs.length - 1], "active");
}
Array.from(imgBox.children).forEach((item, index) => {
this.opacitySwitch(item, 0);
item.style.zIndex = 0;
})
this.opacitySwitch(imgBox.children[this.imgIndex], 100);
imgBox.children[this.imgIndex].style.zIndex = 90;
};
// 所有操作函數
onChange = () => {
const {
iconBox } = this.state;
const {
root, type } = this.props;
iconBox.onmouseover = (e) => {
if (e.target.nodeName !== "LI") return;
clearInterval(this.autoTimer);
const index = [].indexOf.call(e.target.parentNode.children, e.target);
this.imgIndex = type === "move" ? index + 1 : index;
this.animate();
this.autoMove(); //重新啓動
};
root.onclick = (e) => {
if (e.target.className?.indexOf("banner-right-btn") > -1) {
clearInterval(this.autoTimer);
this.imgIndex++;
this.animate();
this.autoMove();
} else if (e.target.className?.indexOf("banner-left-btn") > -1) {
clearInterval(this.autoTimer);
this.imgIndex--;
this.animate();
this.autoMove();
}
};
};
//透明度切換
opacitySwitch = (ele, target) => {
let num = 10;
clearInterval(this.animateTimer);
this.animateTimer = setInterval(() => {
let speed = target > num ? 5 : -5;
//剩餘可運動量 <= 每次所走的量
if (Math.abs(target - num) <= Math.abs(speed)) {
clearInterval(this.animateTimer); //結束運動
ele.style.opacity = target / 100; //到達終點
} else {
num += speed;
ele.style.opacity = num / 100;
}
}, 30);
};
//滾動條移動
scrollMove() {
const {
box, imgBox } = this.state;
const {
width } = this.props;
clearInterval(this.animateTimer); //清除計算器
let minStep = 0; //起始步數
let maxStep = 20; //最大步數
let start = box.scrollLeft; //運動起始位置
let end = this.imgIndex * width; //結束位置
let everyStep = (end - start) / maxStep; //每一步的距離
this.animateTimer = setInterval(() => {
minStep++;
if (minStep >= maxStep) {
//判斷到達最大步數
clearInterval(this.animateTimer); //清除計算器
}
start += everyStep; //起始位置加上走的距離
box.scrollLeft = start;
}, 20);
}
}
// 設置css
function setStyle(root, style = {
}) {
Object.keys(style).forEach(
(item) => (root.style[item] = getStyleValue(item, style[item]))
);
}
// 特殊處理設置寬高樣式 數字
function getStyleValue(key = "", value) {
const arr = ["height", "width", "top", "left", "right", "bottom", "border"];
return arr.indexOf(key.toLowerCase()) > -1 &&
value !== 0 &&
value &&
!isNaN(value)
? value + "px"
: value;
}
// 增加類名
function addClass(ele, value) {
if (!ele.className) {
ele.className = value;
} else {
newClassName = ele.className;
newClassName += " ";
newClassName += value;
ele.className = newClassName;
}
}
// 刪除class函數
function removeClassName(ele, className) {
let str = ele.className,
index = str.indexOf(className);
if (index > -1) {
ele.className = str.replace(className, "");
}
}
css 部分寫的稍微簡陋一些
* {
margin: 0;
padding: 0;
}
li {
list-style: none;
}
.banner {
position: relative;
}
.banner-img-box-move {
overflow: hidden;
height: 100%;
width: 100%;
position: relative;
}
.banner-img-box-move > ul > li > img {
height: 100%;
width: 100%;
}
.banner-img-box-move > ul > li {
float: left;
}
.banner-icon-box {
position: absolute;
right: 20px;
bottom: 20px;
z-index: 99;
display: flex;
}
.banner-icon-box > li {
height: 15px;
width: 15px;
margin-right: 12px;
background-color: #eee;
border-radius: 50%;
}
.banner-img-box-transparency {
position: relative;
}
.banner-img-box-transparency > ul > li {
position: absolute;
left: 0;
top: 0;
}
.banner-img-box-transparency> ul > li >img{
height: 100%;
width: 100%;
}
.btn {
position: absolute;
height: 40px;
width: 40px;
z-index: 99;
}
.banner-left-btn {
left: 0;
top: 50%;
transform: translateY(-50%);
}
.banner-right-btn {
right: 0;
top: 50%;
transform: translateY(-50%);
}
.active {
background-color: red !important;
}
使用
<script>
const imgs = ['images/1.jpg','images/2.jpg','images/3.jpg','images/4.jpg','images/5.jpg',]
const root = document.querySelector('#root');
new Banner({
imgs,
root,
type: 'transparency', //move是滾動輪播, transparency是透明度
width: 1000,
height: 400,
})
</script>