import type { Options, MoevOpts } from './types/index'
import type { App, Window } from './types/base'
/*!* module-util */
const isDOM = (app: App): app is HTMLElement => {
return app instanceof HTMLElement
}
/*!* module-main */
class Swiper {
public option: Options
private mainEl: HTMLElement
private active: number
private swiperWidth: number
private elements: Element[]
private transition: number
private total: number
constructor (app: App, option: Options) {
this.option = Object.assign({space: 30, loop: true}, option)
this.transition = 300
this.mainEl = this.getNode(app)
if (!this.mainEl) new Error('未知节点。')
this.elements = this.initSwiperEl(this.mainEl, this.option)
const swiper = this.getNode('.swiper-wrapper', this.mainEl)
this.initMoveEvent()
this.resize(swiper)
this.computeInit(swiper, this.elements, this.option)
const { nextNode, prevNode } = this.getSiblingEl(option)
this.initsiblingEvent(prevNode, 'prev')
this.initsiblingEvent(nextNode, 'next')
this.initPosition(swiper)
this.initPaginationEl()
window.addEventListener('resize', () => this.resize(swiper))
window.addEventListener('load', () => this.resize(swiper))
}
// 初始化计算选中,总数
private computeInit (swiper: HTMLElement, elements: Element[], option: Options) {
let active: number = option.loop ? 1 : 0
this.total = option.loop ? elements.length - 2 : elements.length - 1
Object.defineProperty(this, 'active', {
set (newNum: number): void {
swiper.style.transitionDuration = `${this.transition}ms`
swiper.style.transform = `translate3d(-${ (this.swiperWidth)* newNum + (this.option.space * newNum)}px, 0px , 0px)`
active = newNum
this.setSiblingStyle(active, option)
setTimeout(() => {
this.transition = 300
})
},
get (): number {
return active
}
})
this.setSiblingStyle(active, option)
}
// 设置左/右按钮样式
private setSiblingStyle (active: number, option: Options) {
const { nextNode, prevNode } = this.getSiblingEl(option)
if (!option.loop) {
if (active >= this.total) {
nextNode.classList.add('swiper-button-disabled')
} else {
nextNode.classList.remove('swiper-button-disabled')
}
if (active <= 0) {
prevNode.classList.add('swiper-button-disabled')
} else {
prevNode.classList.remove('swiper-button-disabled')
}
}
}
// 初始化生成节点
private initSwiperEl (el: HTMLElement, option: Options): Array {
const swiper = this.getNode('.swiper-wrapper', el)
if (!swiper) new Error('.swiper-wrapper')
const list = Array.from(swiper.children)
list.forEach((item, index) => {
item.setAttribute('data-index', (index + 1).toString())
})
if (option.loop) { // 是否使用了无限循环
const before = list[0].cloneNode(true) as HTMLElement
before.classList.add('swiper-slide-duplicate')
before.setAttribute('data-index','1')
const after = list[list.length - 1] .cloneNode(true) as HTMLElement
after.setAttribute('data-index', list.length.toString())
after.classList.add('swiper-slide-duplicate')
swiper.insertBefore(after, list[0])
swiper.insertBefore(before, list[list.length - 1].nextElementSibling)
list.push(before)
list.unshift(after)
}
list.forEach((node, index) => {
if (index !== 0 && isDOM(node))
node.style.marginLeft = '30px'
})
return list
}
// 初始分页按钮样式
private initPaginationEl (): void {
const { pagination: { el } } = this.option
const Pagination = this.getNode(el, this.mainEl)
Pagination.classList.add(...['swiper-pagination-clickable', 'swiper-pagination-bullets', 'swiper-pagination-horizontal'])
let once: boolean = false
this.elements.forEach((node, index) => {
if (!node.classList.contains('swiper-slide-duplicate')) {
const oSpan = document.createElement('span')
oSpan.classList.add('swiper-pagination-bullet')
oSpan.setAttribute('data-slid', index.toString())
oSpan.addEventListener('click', () => {
this.active = Number(oSpan.getAttribute('data-slid'))
this.paginationSet(oSpan, Pagination)
})
Pagination.appendChild(oSpan)
if (!once) {
once = true
oSpan.classList.add('swiper-pagination-bullet-active')
}
}
})
}
// 左右箭头 事件挂载
private initsiblingEvent (node: Element, direction: string) {
node.addEventListener('click', () => this.onclick(direction))
}
// 初始化拖动事件, 拖动事件
private initMoveEvent () {
let opts: MoevOpts = {
node: null,
pageX: 0,
offsetLeft: 0,
clientWidth: 0,
flag: false,
direction: 'next',
movePos: 0,
time: 0
}
const swiper = this.getNode('.swiper-wrapper', this.mainEl)
// 移动事件
const mousemove = (ev: MouseEvent) => {
ev.preventDefault()
ev.stopPropagation()
if (opts.node) {
const movePageX = ev.pageX
const pos = opts.pageX - movePageX
opts.movePos = pos
// 是否超过一半距离
opts.flag = Math.abs(pos) >= Math.round(opts.clientWidth / 2)
opts.direction = pos >= 0 ? 'next' : 'prev'
swiper.style.transitionDuration = '0ms'
const left = opts.offsetLeft + pos
swiper.style.transform = `translate3d(${left < 0 ? '' : '-'}${Math.abs(left) + (this.option.space * this.active)}px, 0px , 0px)`
}
}
// 抬起事件
const mouseup = () => {
const time = ((new Date).getTime() - opts.time)
let num = Math.round(Math.abs(opts.movePos) / opts.clientWidth)
if (opts.direction === 'next') {
num = Math.min((this.active + num), this.elements.length - 1)
} else {
num = Math.max(this.active - num, 0)
}
if (opts.flag || time < 300 && time > 50 && Math.abs(opts.movePos) > 3) {
this.onclick(opts.direction, time < 300 ? -1 : num)
} else {
swiper.style.transitionDuration = '300ms'
swiper.style.transform = `translate3d(-${opts.offsetLeft + (this.option.space * this.active)}px, 0px , 0px)`
}
opts.clientWidth = 0
opts.pageX = 0
opts.movePos = 0
opts.flag = false
window.removeEventListener('mouseup', mouseup)
window.removeEventListener('mousemove', mousemove)
}
// 按下事件
const mousedown = (ev: MouseEvent, node: Element) => {
opts.time = (new Date).getTime()
opts.node = node
opts.pageX = ev.pageX
opts.clientWidth = node.clientWidth
opts.offsetLeft = opts.clientWidth * this.active
}
swiper.addEventListener('mousedown', (event: MouseEvent) => {
if (event.button !== 0) return false
const node = this.elements[this.active]
if (node.classList.contains('swiper-slide-duplicate') && node instanceof HTMLElement && node.dataset.index) {
this.transition = 0
this.active = Number(node.dataset.index)
}
mousedown(event, swiper)
window.addEventListener('mousemove', mousemove)
window.addEventListener('mouseup', mouseup)
})
}
// 初始化位置
private initPosition (swiper: HTMLElement) {
swiper.style.transitionDuration = `0ms`
swiper.style.transform = `translate3d(-${this.swiperWidth * this.active + (this.option.space * this.active)}px, 0px , 0px)`
}
// 分页按钮样式 设置
private paginationSet (node: HTMLElement | Element, Pagination: HTMLElement): void {
Array.from(Pagination.children).forEach(item => {
if (item.classList.contains('swiper-pagination-bullet-active')) {
item.classList.remove('swiper-pagination-bullet-active')
}
})
node.classList.add('swiper-pagination-bullet-active')
}
// 初始化事件
private getSiblingEl (option: Options): {[key: string]: Element} {
const {navigation: { nextEl, prevEl }} = option
const nextNode = this.getNode(nextEl)
const prevNode = this.getNode(prevEl)
return { nextNode, prevNode }
}
// 点击函数
onclick (direction: string, num: number = -1) {
const {pagination: { el }, loop} = this.option
const node = this.elements[this.active]
let newNum = 0
if ('next' === direction) {
newNum = ~num ? num : this.active + 1
} else {
newNum = ~num ? num : this.active - 1
}
if (!loop) {
if (newNum > this.total) newNum = this.total
if (newNum < 0) newNum = 0
}
const delay = (ac1: number, ac2: number) => {
this.active = ac1
setTimeout(() => {
this.transition = 300
this.active = ac2
})
}
if (node.classList.contains('swiper-slide-duplicate')) {
if (~[node.previousElementSibling, node.nextElementSibling].indexOf(this.elements[newNum])) {
this.active = newNum
} else{
this.transition = 0
if (newNum > 0) {
delay(1, 2)
} else {
delay(this.total, this.total - 1)
}
}
} else {
this.active = newNum
}
const Pagination = this.getNode(el, this.mainEl)
const index = this.elements[this.active].getAttribute('data-index')
const curent = Pagination.children[Number(index) - 1]
if (curent) {
this.paginationSet(curent, Pagination)
}
}
// 获取节点
getNode (app: App, doc: HTMLElement | Document = document): HTMLElement {
if (isDOM(app)) return app
if (typeof app === 'string') return doc.querySelector(app)
return doc.querySelector('.mySwiper')
}
// 获取 窗口变化大小
resize (swiper: HTMLElement) {
this.swiperWidth = swiper.offsetWidth
this.initPosition(swiper)
}
}
(()=> {
const E: Window = window || globalThis
if (E) E.Swiper = Swiper
})()
type App = string | HTMLElement
interface Window {
[key: string]: unknown;
}
export {
App,
Window
}
interface Pagination {
el: string
clickable?: boolean
}
interface Navigation {
nextEl: string
prevEl: string
}
interface Options {
loop?: Boolean
space: number,
pagination: Pagination
navigation: Navigation
}
interface MoevOpts {
node: Element | null
pageX: number
offsetLeft: number
clientWidth: number
flag: Boolean | true
direction: string
time: number
movePos: number,
}
export {
Options,
Navigation,
Pagination,
MoevOpts
}
Swiper demo