基于Vue3造轮子之实现H5的Picker组件

1,666 阅读5分钟

“这是我参与8月更文挑战的第7天,活动详情查看: [8月更文挑战]”

Vue3出现一段时间了,也有看过基础知识,所以准备小小造一个轮子。Picker组件的话,是H5里面比较常用的一个组件,这里就拿它开刀,实现一个非常简单的Picker组件(当时别人问我自己有写过插件没,我说开源有很多能用的,还没遇到自己写的,就被一脸鄙视了,于是也动动手)。

废话不多说,先看看实现的效果图:

nicey.gif

开发环境

这里的话还是用的Vue3.0的版本,还没有去用script setup这种语法(虽然3.0也支持-_-||)。

"dependencies": {
    "vue": "^3.0.0"
},
"devDependencies": {
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "typescript": "~4.1.5"
}

实现步骤

实现一个Picker插件分仨步骤: 创建自定义插件 -> 实现插件的功能 -> 使用插件

自定义插件的创建

Vue中,插件其实就是一个对象且这个对象里面有一个install方法,这个方法可以接受一个Vue实例

import { App } from 'vue'
import Picker from './Picker.vue'

const pickerPlugin = {
  install: (app: App) => {
    app.component('x-picker', Picker)
  }
}

export default pickerPlugin

这里,我通过自定义插件的方式创建了一个全局组件,这个组件就是我们接下来要写的Picker组件。

创建组件Picker

最简单的Picker组件至少都应该接收一个list数组和一个可修改显示状态的value,所以这里的props我是这样写的:

props: {
    list: {
      type: Array,
      default: () => []
    },
    modelValue: {
      type: Boolean,
      default: false
    }
}

我这里接收的是modelValue,所以父组件可以通过v-model的方式来双向绑定使用。

接着,在思考过后,Picker组件包含两个部分:一个是渲染列表并实现拖动,第二个就是派发事件显示或隐藏。所以,我这里就抽成了两个方法,具体的实现就在方法里面写。

这样也是利用了CompositionAPI的特点嘛,写起来是挺舒服的。

import useList from './list'
import useEvent from './event'

setup(props, { emit }) {
    const { ctx }: any = getCurrentInstance()
    const $useList = useList(props)
    const $useEvent = useEvent({ emit, ctx })
    return {
      ...$useList,
      ...$useEvent
    }
}

组件列表的拖动实现

组件拖动的话,我们得知道Vue提供的@touchstart@touchmove两个手势方法。
通过记录点击时的位置和手指移动时的位置,计算差值就可以获得移动的一个距离;然后通过修改transform的方式来使得列表移动。

当然在实现时我还是推荐弄一个函数节流,因为move的方法触发频率挺高。

touchstart(e: TouchEvent) {
    // 记录开始的位置 e.touches[0].clientY
},
touchmove(e: TouchEvent) {
    // 计算差值offY,然后修改列表transform的translate属性
},
const getOffsetY = computed(() => {
  return {
    transform: `translate(-50%, ${offY.value}px)`
  }
})

这样的话,是能实现拖动效果得,但是却发现一个很严重的问题,就是移动时列表一顿一顿的,于是我添加了transition属性来过渡,虽然改善了一些,但效果还是存在很大问题。

于是,于是我就去VantUI官网看了看VantUI Picker组件,发现他们的过渡是这样写的:

image.png

transition-timing-function: cubic-bezier(0.23, 1, 0.68, 1);

然后,我加了这个后,发现效果是比之前棒很多了,但是操作的感觉比起VantUI还是差了很多,这个问题还没发现怎么回事,应该是实现的方案存在问题吧。

组件拖动后回弹

完成组件拖动后,我们发现一个问题就是,拖动超出顶部和底部是存在问题的。所以,需要给一个安全距离,当超过这个距离后,就不允许拖动。并且,超过顶部或底部就要回弹到边界上去。

通过@touchend事件,就可以很好的解决该问题:

touchend() {
    setTimeout(() => {
        offY.value = 边界的值
    }, 100)
}

只要在手势事件结束后,重新设置组件偏移的位置即可。这里加setTimeout的原因是因为:
有时候发现操作快了之后,计算属性得到的结果是对的,但是视图渲染的位置不对。

事件触发/显示隐藏

完成了拖动的功能后,就得去完成触发事件和显示隐藏的功能了,这个也比较简单:

  1. 事件的触发我们可以通过setup里面的第二个参数解构获得emit方法来实现:
setup(props, { emit }) {
    handleConfirm() {
        emit('confirm', ctx.)
    }
}
  1. v-model的实现同样也利用emit方法来派发一个事件,父组件使用v-model后会自动监听该事件并做对应的赋值操作:
setup(props, { emit }) {
    handleConfirm() {
        emit('update:modelValue', bool)
    }
}
  1. 通过事件我们可以修改组件的状态(显示/隐藏),现在就要做显示和隐藏这个功能。利用常规的v-show我们就可以很轻易的达到目的,但是这样显得很生硬,所以这里利用了transition组件:
.slide-picker-enter-active, .slide-picker-leave-active {
  transition: all .5s;
}
.slide-picker-enter-from, .slide-picker-leave-to {
  transform: translateY(100%);
}

这里有一点,v-enter变为了v-enter-from了。

到目前位置,我们的插件就完成了,接下来就可以试试发布到npm啦。

发布到NPM

哈哈,完成了一个小插件后,就可以发布到npm里面存起来。当然,发布的方式也很简单:

  1. 把源换成官方的源:
    我这里使用的是nrm,所以直接nrm use npm即可;

  2. 登录npm
    npm login,按提示输入账号,密码,邮箱即可;

  3. 发布包: 在发布前,去npm官网查看有没有重复的包名(重复会发布不成功),包名合法的话,然后npm publish即可成功发布。

然后,等一下下,你就可以在npm的官网中看到自己的包啦;接着,你就可以通过npm的方式引用了:

npm isntall --save xxx`
import xxx from 'xxx'
app.use(xxx)

总结

我这里只是完成了最简单的一个Picker插件,效果也并不完美,但是过程还是学到很多的;
具体代码放在了Git仓库中:Git仓库地址; 哈哈哈,完成一个轮子的简单制作还是挺开心啦!