# HTML 结构
- HTML 结构搭建
<!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> | |
<link rel="stylesheet" href="./index.css"> | |
</head> | |
<body> | |
<div class="swiper"> | |
<div class="swiper-items"> | |
<div class="swiper-item swiper-item1"> | |
<img src="./images/808.jpg" alt=""> | |
</div> | |
<div class="swiper-item swiper-item2"> | |
<img src="./images/821.jpg" alt=""> | |
</div> | |
<div class="swiper-item swiper-item3"> | |
<img src="./images/822.png" alt=""> | |
</div> | |
<div class="swiper-item swiper-item4"> | |
<img src="./images/823.png" alt=""> | |
</div> | |
</div> | |
<div class="indicator-dots"></div> | |
<div class="btn-left">Left</div> | |
<div class="btn-right">Right</div>' | |
</div> | |
<script src="./animation.js"></script> | |
<script src="./index.js"></script> | |
</body> | |
</html> |
# Stylus 样式
- Stylus :
stylus -w index.styl -o index.css
* | |
margin 0 | |
padding 0 | |
.active | |
background-color skyblue !important | |
.swiper | |
position relative | |
margin 120px auto | |
width 800px | |
height 500px | |
overflow hidden | |
background-color pink | |
box-sizing border-box | |
.swiper-items | |
position relative | |
// width 3000px | |
height 500px | |
background-size cover | |
img | |
width 100% | |
height 100% | |
.swiper-item | |
float left | |
width 800px | |
height 500px | |
.swiper-item1 | |
background-color red | |
.swiper-item2 | |
background-color purple | |
.swiper-item3 | |
background-color pink | |
.swiper-item4 | |
background-color yellow | |
.indicator-dots | |
display flex | |
justify-content center | |
align-items center | |
position absolute | |
bottom 20px | |
left 50% | |
transform translateX(-50%) | |
width 100% | |
height 30px | |
&>div | |
cursor pointer | |
margin 0 5px | |
width 10px | |
height 10px | |
border-radius 50% | |
border 1px solid #ccc | |
background-color #fff | |
&hover | |
background-color skyblue | |
.btn-left | |
.btn-right | |
cursor pointer | |
color #fff | |
position absolute | |
top 50% | |
transform translateY(-50%) | |
width 50px | |
height 40px | |
text-align center | |
line-height 40px | |
background-color rgba(255, 255, 255, 0.486) | |
transition all .3s | |
&hover | |
background-color #ccc | |
.btn-left | |
left 0 | |
border-top-right-radius 10% | |
border-bottom-right-radius 10% | |
.btn-right | |
right 0 | |
border-top-left-radius 10% | |
border-bottom-left-radius 10% |
# CSS 样式
- 编译后的 CSS 样式
* { | |
margin: 0; | |
padding: 0; | |
} | |
.active { | |
background-color: #87ceeb !important; | |
} | |
.swiper { | |
position: relative; | |
margin: 120px auto; | |
width: 800px; | |
height: 500px; | |
overflow: hidden; | |
background-color: #ffc0cb; | |
box-sizing: border-box; | |
} | |
.swiper .swiper-items { | |
position: relative; | |
height: 500px; | |
background-size: cover; | |
} | |
.swiper .swiper-items img { | |
width: 100%; | |
height: 100%; | |
} | |
.swiper .swiper-item { | |
float: left; | |
width: 800px; | |
height: 500px; | |
} | |
.swiper .swiper-item1 { | |
background-color: #f00; | |
} | |
.swiper .swiper-item2 { | |
background-color: #800080; | |
} | |
.swiper .swiper-item3 { | |
background-color: #ffc0cb; | |
} | |
.swiper .swiper-item4 { | |
background-color: #ff0; | |
} | |
.indicator-dots { | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
position: absolute; | |
bottom: 20px; | |
left: 50%; | |
transform: translateX(-50%); | |
width: 100%; | |
height: 30px; | |
} | |
.indicator-dots>div { | |
cursor: pointer; | |
margin: 0 5px; | |
width: 10px; | |
height: 10px; | |
border-radius: 50%; | |
border: 1px solid #ccc; | |
background-color: #fff; | |
} | |
.indicator-dots>divhover { | |
background-color: #87ceeb; | |
} | |
.btn-left, | |
.btn-right { | |
cursor: pointer; | |
color: #fff; | |
position: absolute; | |
top: 50%; | |
transform: translateY(-50%); | |
width: 50px; | |
height: 40px; | |
text-align: center; | |
line-height: 40px; | |
background-color: rgba(255,255,255,0.486); | |
transition: all 0.3s; | |
} | |
.btn-left:hover, | |
.btn-right:hover { | |
background-color: #ccc; | |
} | |
.btn-left { | |
left: 0; | |
border-top-right-radius: 10%; | |
border-bottom-right-radius: 10%; | |
} | |
.btn-right { | |
right: 0; | |
border-top-left-radius: 10%; | |
border-bottom-left-radius: 10%; | |
} |
# animate 动画
- 封装动画效果,支持左右上下滚动
function animate(obj, target, callback, offsetDirection = 'offsetLeft', delay = 10) { | |
if (obj.timer) clearInterval(obj.timer) | |
let timer = null | |
// 实现 timing-function 效果 | |
function calcStep(target, x = 10) { | |
let step = (target - obj[offsetDirection]) / x | |
step = step > 0 ? Math.ceil(step) : Math.floor(step) | |
return step | |
} | |
// 判断哪种滚动方式 | |
const offset = () => offsetDirection === 'offsetTop' | |
//setInterval 执行的回调函数 | |
const handler = () => { | |
// 如果到达目的地清除定时器且判断后是否执行 callback 函数 这里用于节流 | |
if (obj[offsetDirection] == target) { | |
clearInterval(obj.timer) | |
callback && callback() | |
} else { | |
if (offset()) obj.style.top = obj[offsetDirection] + calcStep(target) + 'px' | |
else obj.style.left = obj[offsetDirection] + calcStep(target) + 'px' | |
} | |
} | |
obj.timer = setInterval(handler, delay); | |
} |
# index.js
- 操作元素实例
window.addEventListener('load', loadCallback) | |
function loadCallback () { | |
// 将选择器以对象形式存储 | |
const options = { | |
swiper: '.swiper', | |
swiperItem: '.swiper-item', | |
swiperItems: '.swiper-items', | |
indicatorDot: '.indicator-dot', | |
indicatorDots: '.indicator-dots', | |
btnLeft: '.btn-left', | |
btnRight: '.btn-right' | |
} | |
class OperatingElements { | |
constructor(options) { | |
const { swiper, swiperItems, indicatorDots, btnLeft, btnRight } = options | |
this.flag = true // 控制节流阀开关 | |
this.options = options // 所有选择器 | |
this.swiper = this.getElement(swiper) // 获取 class -> swiper 盒子 | |
this.swiperItem = this.getElement(swiperItems) // 获取 class -> swiper-swiperItems 盒子 | |
this.indicatorDot = this.getElement(indicatorDots) // 获取指示器 | |
this.btnLeft = this.getElement(btnLeft) // 获取做左按钮 | |
this.btnRight = this.getElement(btnRight) // 获取做右按钮 | |
// 这里需要先等 this.swiperItems 全部获取图片父盒子元素到再执行获取第一张图片盒子宽度 | |
this.init(() => this.swiperItemWidth = this.getElWidth(this.swiperItems)) | |
this.appendChild(this.swiperItem, this.swiperItemFirst) // 追加克隆的图片到最后面 | |
this.getSwiperItems() // 再次获取所有图片父盒子宽度 | |
this.indicatorDots[0].className = 'indicator-dot active' // 默认第一个小圆圈加样式 | |
} | |
// 再次初始化 | |
init(callback) { | |
const { swiperItem, swiperItems, indicatorDot, indicatorDots } = this.options | |
this.swiperItems = this.getElement(swiperItem, true) // 获取所有图片父盒子 | |
this.swiperItemFirst = this.cloneEl(this.swiperItems[0]) // 克隆第一张 | |
callback && callback() // 回调 | |
this.indicatorDots = this.getElement(indicatorDot, true) // 获取所有指示器 | |
this.createIndicatorDots(this.swiperItems) // 动态创建所有指示器 | |
} | |
// 获取所有 calss 是 swiper-item 的元素 | |
getSwiperItems() { | |
this.swiperItems = this.getElement(this.options.swiperItem, true) | |
} | |
// 传入选择器获取元素 | |
getElement(selector, isAll = false) { | |
if (isAll) return document.querySelectorAll(selector) | |
return document.querySelector(selector) | |
} | |
// 获取元素宽度 | |
getElWidth(target) { | |
if (Object.prototype.toString.call(this.swiperItems) === '[object NodeList]') { | |
return target[0].offsetWidth | |
} | |
return target.offsetWidth | |
} | |
// 克隆元素 | |
cloneEl(targetEl, deep = true) { | |
return targetEl?.cloneNode(deep) | |
} | |
// 插入元素 并再次给计算 class 是 swiper-item 盒子的宽度 | |
appendChild(parentEl, cihldEl, isExec) { | |
parentEl && parentEl.appendChild(cihldEl) | |
if (isExec) return | |
this.setSwiperItemsWidth() | |
} | |
// 创建元素 | |
createEl(type) { | |
return document.createElement(type) | |
} | |
// 设置属性 | |
setAttribute(target, key, value) { | |
target.setAttribute(key, value) | |
} | |
// 设置 class 是 swiper-item 的盒子元素宽度 | |
setSwiperItemsWidth(swiperItems, targetEl) { | |
targetEl ||= this.getElement(this.options.swiperItems) | |
swiperItems ||= this.getElement(this.options.swiperItem, true) | |
const width = swiperItems.length * this.getElWidth(swiperItems) | |
targetEl.style.width = width + 'px' | |
} | |
// 动态追加小圆圈 | |
createIndicatorDots(swiperItems) { | |
for (let i = 0; i < swiperItems.length; i++) { | |
const indicatorDot = this.createEl('div') | |
this.setAttribute(indicatorDot, 'class', 'indicator-dot') | |
this.appendChild(this.indicatorDot, indicatorDot, true) | |
} | |
this.indicatorDots = this.getElement(this.options.indicatorDot, true) | |
} | |
// 清除 class 为 indicator-dot 的所有样式 | |
resetClassName() { | |
for (let i = 0; i < this.indicatorDots.length; i++) { | |
this.indicatorDots[i].className = 'indicator-dot' | |
} | |
} | |
// 点击的小圆圈追加样式 | |
circleChange() { | |
this.resetClassName() | |
this.indicatorDots[circle].className = 'indicator-dot active' | |
} | |
} | |
// 实例 | |
operatingElements = new OperatingElements(options) | |
let index = 0 | |
// 所有小圆点事件 | |
for (let i = 0; i < operatingElements.indicatorDots.length; i++) { | |
operatingElements.indicatorDots[i].addEventListener('click', function () { | |
operatingElements.resetClassName() | |
operatingElements.indicatorDots[i].className = 'indicator-dot active' | |
index = i | |
animate(operatingElements.swiperItem, -operatingElements.swiperItemWidth * i) | |
}) | |
} | |
let circle = 0 | |
// 左按钮 | |
function btnLeftClick() { | |
if (operatingElements.flag) { | |
operatingElements.flag = false | |
if (index == 0) { | |
index = operatingElements.indicatorDots.length | |
operatingElements.swiperItem.style.left = -index * operatingElements.swiperItemWidth + 'px' | |
} | |
index-- | |
animate(operatingElements.swiperItem, operatingElements.swiperItemWidth * -index, () => operatingElements.flag = true) | |
circle-- | |
if (circle < 0) circle = operatingElements.indicatorDots.length - 1 | |
operatingElements.circleChange() | |
} | |
} | |
// 右按钮 | |
function btnRightClick() { | |
if (operatingElements.flag) { | |
operatingElements.flag = false | |
if (index == operatingElements.indicatorDots.length) { | |
operatingElements.swiperItem.style.left = 0 | |
index = 0 | |
} | |
index++ | |
// 执行动画函数 计算动画的距离 传入回调函数进行节流阀操作 | |
animate(operatingElements.swiperItem, -operatingElements.swiperItemWidth * index, () => operatingElements.flag = true) | |
circle++ | |
if (index == operatingElements.indicatorDots.length) circle = 0 | |
operatingElements.circleChange() | |
} | |
} | |
// 左右按钮点击事件 | |
operatingElements.btnLeft.addEventListener('click', btnLeftClick) | |
operatingElements.btnRight.addEventListener('click', btnRightClick) | |
// 自动播放 间隔 3000ms | |
let timer = null | |
function autoplay() { | |
timer = setInterval(() => { | |
operatingElements.btnRight.click() | |
}, 3000) | |
} | |
autoplay() | |
// 进入隐藏按钮 | |
operatingElements.swiper.addEventListener('mouseenter', () => { | |
operatingElements.btnLeft.style.display = 'block' | |
operatingElements.btnRight.style.display = 'block' | |
clearInterval(timer) | |
timer = null | |
}) | |
// 离开显示按钮 | |
operatingElements.swiper.addEventListener('mouseleave', () => { | |
operatingElements.btnLeft.style.display = 'none' | |
operatingElements.btnRight.style.display = 'none' | |
autoplay() | |
} ) | |
console.log(operatingElements); | |
} |