模拟swiper 轮播图 练习

 index.ts

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
})()

 base.d.ts

type App = string | HTMLElement
interface Window {
  [key: string]: unknown;
}
 
export {
  App,
  Window
}

 

index.d.ts

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
}

 html



  
    
    Swiper demo
    
    
    
  
    
    
  
  
  
    
    
Slide 1
Slide 2
Slide 3
Slide 4
Slide 5
Slide 6
Slide 7
Slide 8
Slide 9

 

你可能感兴趣的:(前端,javascript,html,typescript)