前端训练营学习笔记——组件化实战3:将动画和手势库应用到轮播图组件及为组件增加更多属性

本文介绍了一种轮播图组件的实现方式,该组件通过手势和动画库支持自动轮播和手动滑动切换,并实现了跨端兼容。同时,组件具备onChange、onClick事件,允许跳转至关联网页,增强了交互性和实用性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 手势动画应用

1.1 将轮播图组件中手动轮播实现替换成通过手势组件实现

    // 手动控制轮播
    this.root.addEventListener("start", (event)=>{
      console.log("start")
    })
    
    this.root.addEventListener("pan", (event)=>{
      console.log("pan")
      let x = event.clientX - event.startX - ax
      let current = this[STATE].position - ((x-x%500)/500) // x - x % 500的值一定是500的倍数(自己把自己多余的给减去)
      for(let offset of [-1, 0, 1]) {
        let pos = current + offset
        pos = (pos%children.length + children.length) % children.length // pos可能是负很多,需要转换到[0, children.length),如将索引-1变为3
        children[pos].style.transition = "none"
        children[pos].style.transform = `translateX(${-pos*500 + offset*500 + x%500}px)`
      }
    })
    
    this.root.addEventListener("end", (event)=>{
        console.log("end")
        let x = event.clientX - startX
        let current = position - Math.round(x/500) // 获取松手时就近的帧索引
        for(let offset of [0, Math.sign(Math.abs(x)>250 ? x:-x)]) { 
          // 拖动距离大于视口的一半,当前图片和下一张图片跟着移动,否则当前图片和上一张图片跟着移动
          let pos = current + offset
          pos = (pos + children.length) % children.length // 将索引-1变为3
          children[pos].style.transition = "" // 恢复过渡效果
          children[pos].style.transform = `translateX(${-pos*500 + offset*500}px)`
        }
    })

1.2 将轮播图中自动轮播实现替换成通过动画组件实现

    let handler = null  

    let nextPicture = ()=>{
      let children = this.root.children
      let nextIdx = (this[STATE].position + 1) % children.length
      let current = children[this[STATE].position]
      let next = children[nextIdx]
      // next快速移入viewport的后一个位置
      next.style.transition = "none"
      next.style.transform = `translateX(${500 - nextIdx*500}px)`

      tl.add(new Animation(current.style, "transform", -this[STATE].position*500, -500-this[STATE].position*500, 500, 0, ease, v=>`translateX(${v}px)`))
      tl.add(new Animation(next.style, "transform", 500 - nextIdx*500, -nextIdx*500, 500, 0, ease, v=>`translateX(${v}px)`))

      this[STATE].position = nextIdx
      this.triggerEvent("change", {position: this[STATE].position})

    }
    handler = setInterval(nextPicture, time)

1.3 将自动轮播和手动轮播利用时间线结合起来

  1. 在start的事件回调中,停止自动轮播的时间线并清除自动轮播定时器
  2. 在pan的事件回调中,为了能从自动轮播自然过渡到手动轮播,需要加上自动轮播的偏移量
  3. 在panend的事件回调中,原有的取巧处理不适合结合时间线,基于pan的回调函数逻辑进行处理
   // 手动控制轮播
    this.root.addEventListener("start", (event)=>{
      console.log("start")
      tl.pause()
      clearInterval(handler)
      let progress = tl.getAnimeRunTime() / 500
      ax = ease(progress) * 500 - 500
    })

    this.root.addEventListener("pan", (event)=>{
      console.log("pan")
      let x = event.clientX - event.startX - ax // 减去动画已移动的距离避免拖动时产生跳变
      let current = this[STATE].position - ((x-x%500)/500) // x - x % 500的值一定是500的倍数(自己把自己多余的给减去)
      for(let offset of [-1, 0, 1]) {
        let pos = current + offset
        pos = (pos%children.length + children.length) % children.length // pos可能是负很多,需要转换到[0, children.length),如将索引-1变为3
        children[pos].style.transition = "none"
        children[pos].style.transform = `translateX(${-pos*500 + offset*500 + x%500}px)`
      }
    })

      this.root.addEventListener('panend', (event) => {        
        // 与时间线结合时基于pan的逻辑修改,原先的逻辑因为偷懒,结构不好调整
        console.log("panend")
        tl.reset()
        tl.start()
        handler = setInterval(nextPicture, time)
        let x = event.clientX - event.startX - ax
        let direction = Math.round((x%500)/500)
        if(event.isFlick) {
          console.log(event.velocity)
          if(direction > 0) {
            direction = Math.ceil((x%500)/500)
          }else if(direction < 0) {
            direction = Math.floor((x%500)/500)
          }
        }
        
        let current = this[STATE].position - ((x-x%500)/500) // x - x % 500的值一定是500的倍数(自己把自己多余的给减去)
        for(let offset of [-1, 0, 1]) {
          let pos = current + offset
          pos = (pos%children.length + children.length) % children.length // pos可能是负很多,需要转换到[0, children.length),如将索引-1变为3
          children[pos].style.transition = "none"
          //children[pos].style.transform = `translateX(${-pos*500 + offset*500 + x%500}px)`
          tl.add(new Animation(children[pos].style, "transform", 
                -pos*500 + offset*500 + x%500,  
                -pos*500 + offset*500 + direction*500,
                500, 0, ease, v=>`translateX(${v}px)`))// 动画从pan结束处开始
        }
        
        this[STATE].position = current - direction
        this[STATE].position = (this[STATE].position%children.length + children.length) % children.length 
        this.triggerEvent("change", {position: this[STATE].position})

      })

其中多次用到2个取余技巧:

  1. 对某数取整:(x-x%500)/500
  2. 将任意数取余转换到[0, n)区间:(pos%children.length + children.length) % children.length

2 为组件添加更多属性

  1. 将carousel类与Component父类重复的方法抽取到父类中
  2. 将position属性和attribute属性利用Symbol抽取到父类中
  3. 给组件添加onChange事件获取position的状态改变
  4. 给组件添加onClick事件跳转到图片链接的网站

3 给组件加入children机制

3.1 内容型children

组件内所写字符串即代表了children,见Button示例

import {Component, createElement, ATTRIBUTE} from "./framework.js"
class Button extends Component{
  constructor() {
    super()
  }
  render() {
    this.root = (<div>{this.child}</div>).render()
    return this.root
  }
  appendChild(child) {
    this.child = child
    this.render()
  }
}
let b = <Button>
  content
</Button>
b.mountTo(document.body)

3.2 模板型children

通过模板函数去生成,如列表项,见List示例

import {Component, createElement, ATTRIBUTE} from "./framework.js"
class List extends Component{
  constructor() {
    super()
  }
  render() {
    this.root = (<div>{this.children}</div>).render() // 要在createElement中加入递归处理数组的逻辑
    return this.root
  }
  appendChild(child) {
    this.template = (child)
    this.children = this[ATTRIBUTE].data.map(this.template) // template是传入的模板回调函数
    this.render()
  }
}

let c = <List data={d}>
  {(record)=>
    <div>
      <img src={record.img}/>
      <a href={record.url}>{record.title}</a>
    </div>
  }
</List>
c.mountTo(document.body)

两种组件的大致生成逻辑类似,如下图所示:

将jsx语法转换成createELement函数
在createElement函数中append子节点
在appendChild函数中设置children属性并对组件自身进行render
在render函数中利用jsx语法构建组件并调用其render
最后返回组件自身

唯一不同的是模板型children组件的children并不能直接得到,而是要利用传入组件的模板函数作用于组件数据得到

4 总结

在本文中我们主要将组件实战化2博客中的手势和动画库应用到了组件实战化1博客中编写的原始轮播图组件中,使我们的轮播图组件能在自动轮播和手动轮播之间自然过渡。由于使用的手势库封装了鼠标操作和触控操作,我们实现的轮播图组件也能够跨端使用。我们还给轮播图组件增加了onChange、onClick事件属性以及表示当前轮播图序号的position属性,可以实现点击轮播图跳转到关联的网页,扩展了组件的功能。最后,简单介绍了给组件添加children的机制,使得我们能够对组件进行嵌套和组合,在组件中加入原生html标签和按照模板生成重复型子组件。

ps:如果觉得此文对你有帮助或启发,请不要吝惜你的点赞和分享,如有疑问,请留言或私信交流

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值