我要造轮子系列 -Vue3放大镜组件

1,320 阅读3分钟

前言

系列前几个组件都是用vue2编写的,这次用最新的vue3编写, vue3也出了一段时间,公司也不会牟然换版本,所以很少实践的机会,那就只能够自己写一个组件实践下,看了一下vue3的文档,最大变化,就是setup函数和Composition Api这块,这次我主要也是说说这部分的使用过程。

另外组件也是常用的组件,一般用在电商类网站产品详情页,鼠标移到图片位置放大图片,具体怎实现就不详说 如图:

屏幕录制2021-07-03 下午10.25.38.gif

说说Vue3 Composition Api

vue2中,我们会在methods,computed,watch,data中等等定义属性和方法,共同处理页面逻辑,我们称这种方式为Options API。相信用过的都知道,方便是挺方便,不过缺点也很明显,当项目越来越大,很可能一个methods下就有二三十个方法, 还得确切地知道方法中可以访问哪些属性以及this关键字的行为,维护起来就十分麻烦了。Composition API(组合API)就是解决这个问题而生的。另外vue3是向下兼容的,也就是说允许我们用vue2的写法在vue3写,但既然是个新东西,我们就要尝试体验,看看有什么改变给自己带来什么便利。

Composition API它类似 react hooks写法。这次我直接用 setup 的语法糖编写这个组件。

setup 语法糖

什么是setup 语法糖?想要使用 setup 模式只要在 script 标签上面加个 setup 属性就可以了。这个模式下不需要 returnexport 就可以在模板中使用。

<script setup>
</script>

编写组件

先上组件完整代码


<script setup>
  <template>
    <div class="imgBox" ref="imgbox">
        <slot></slot>
        <div class="mask"
             v-show="state.isShow"
             :style="{
                 left:state.maskX + 'px',
             top :state.maskY +'px'}">
        </div>
    </div>
    <transition name="fade">
    <div class="zoomBox"
         v-show="state.isShow"
         :style="{left :state.boxX +'px',top: state.boxY +'px',}">
        <img :src="state.imgSrc"
             :style="{
                width:state.zoomImgWidth + 'px',
                height: state.zoomImgHeight + 'px',
                marginLeft :-state.bImgX + 'px',
                marginTop : -state.bImgY + 'px'
               }">
    </div>
    </transition>
</template>

<script setup>
    import {defineProps, onMounted, onUnmounted, reactive, ref} from 'vue'
    const maskWidth = 50,
    maskHeight = 50
    const imgbox = ref(null)
    const props = defineProps({mode: String})
    const state = reactive({
        zoomImgWidth: 0,
        zoomImgHeight: 0,
        isShow: false,
        boxX: 0,
        boxY: 0,
        maskX: 0,
        maskY: 0,
        bImgX: 0,
        bImgY: 0,
        imgSrc: ''
    })
    const zommIn = (ev) => {
        const img = ev.currentTarget.getElementsByTagName('img')[0]
        const {offsetWidth, offsetTop, offsetHeight} = imgbox.value
        state.zoomImgWidth = offsetWidth * 3
        state.zoomImgHeight = offsetHeight * 3
        state.imgSrc = img.src
        state.boxX = offsetWidth
        state.boxY = offsetTop
        state.isShow = true
    }
    const zoomMove = (ev) => {
        const {clientX, clientY} = ev
        const {offsetTop, offsetLeft} = ev.currentTarget
        const {offsetWidth, offsetHeight} = imgbox.value
        let mx = clientX - offsetLeft - (maskWidth / 2),
            my = clientY - offsetTop - (maskHeight / 2)
        mx = mx < 0 ? 0 : mx
        my = my < 0 ? 0 : my
        if (mx > offsetWidth - maskWidth / 2) {
            mx = offsetWidth - maskWidth / 2
        }
        if (my > offsetHeight - maskHeight / 2) {
            my = offsetHeight - maskHeight / 2
        }
        state.maskX = mx
        state.maskY = my

        state.bImgX = mx * (state.zoomImgWidth - offsetWidth) / (offsetWidth - maskWidth)
        state.bImgY = my * (state.zoomImgWidth - offsetWidth) / (offsetWidth - maskWidth)
    }
    const zommOut = () => {
        state.isShow = false
    }
    //绑定方法
    onMounted(() => {
        imgbox.value.addEventListener('mouseover', zommIn)
        imgbox.value.addEventListener('mousemove', zoomMove)
        imgbox.value.addEventListener('mouseout', zommOut)
    })
    onUnmounted(() => {
        imgbox.value.removeEventListener('mouseover', ()=>{})
        imgbox.value.removeEventListener('mousemove', ()=>{})
        imgbox.value.removeEventListener('mouseout', ()=>{})
    })
</script>

<style scoped>
    .mask {
        width: 50px;
        height: 50px;
        background-color: #fff;
        opacity: .6;
        cursor: crosshair;
        position: absolute;
    }

    .imgBox {
        position: relative;
        overflow: hidden;
        width: 200px;
        height: 200px;
    }

    .zoomBox {
        width: 200px;
        height: 200px;
        position: absolute;
        border: 1px solid #eee;
        background: #fff;
        overflow: hidden;
    }
    .fade-enter-active, .fade-leave-active {
        transition: opacity .5s
    }
    .fade-enter, .fade-leave-active {
        opacity: 0
    }
</style>

</script>

看到这种模式编程,不就是函数式编程吗!!接下来我就说说我是怎样使用这些API

1.依赖注入defineProps, onMounted, onUnmounted, reactive, ref 这些函数都是在这个组件用到的,看到函数的命名,是不是有点似曾相识的感觉呢,没错,除了reactive比较陌生 其他就是你想的!

    import {defineProps, onMounted, onUnmounted, reactive, ref} from 'vue'

2.defineProps 这个相当于props,组件传入的参数,然后通过保存props变量读取

    const props = defineProps({mode: String})
  1. reactive 这个方式类似于我们设置的data选项, 但绑定到模板上就需要带上state 这个函数目的是为了观察引用数据类型。想直接绑定变量到模板上可以通过toRefs解构模板变量。
       const state = reactive({
        zoomImgWidth: 0,
        zoomImgHeight: 0,
        isShow: false,
        boxX: 0,
        boxY: 0,
        maskX: 0,
        maskY: 0,
        bImgX: 0,
        bImgY: 0,
        imgSrc: ''
    })

4.ref这个也是类似设置data的选项, 但它一般是观察原始数据类型,另外还可以类似vue2通过绑定ref获取dom信息,这次的ref我也是用在获取dom信息,当然你也可以用来设置可观察数据

     // 绑定ref
    <div class="imgBox" ref="imgbox"></div>
    //获取dom信息
    const imgbox = ref(null)

5.onMountedonUnmounted 这两个方法类似 mounted 的钩子函数, 相信熟悉vue的对钩子应该也不会陌生,这里我直接做事件监听

onMounted(() => {
        imgbox.value.addEventListener('mouseover', zommIn)
        imgbox.value.addEventListener('mousemove', zoomMove)
        imgbox.value.addEventListener('mouseout', zommOut)
    })
    onUnmounted(() => {
        imgbox.value.removeEventListener('mouseover', ()=>{})
        imgbox.value.removeEventListener('mousemove', ()=>{})
        imgbox.value.removeEventListener('mouseout', ()=>{})
    })

组件调用

组件调用也简化了,直接import进来,不用在components注册组件。

    import  zoom  from  './components/zoom.vue'
      <zoom>
        <img alt="Vue logo" src="./assets/logo.png"/>
    </zoom>

最后

vue3还提供很多api 在这个组件没用到 ,如emit、watch、computed、provide、inject、还有各生命周期钩子函数,这些都是在vue2 熟悉的东西 ,但在vue3允许你用其他形式来编写,喜欢这种编写方式,就会感觉写得很爽。 对于用react的同学也是可以无缝接入,很快就可以上手,反之在vue3用过Composition Api的同学去学习react hooks 也是很容易上手。