uniapp+vue3 setup+ts 开发小程序实战(用户指引蒙层实现)-灵析社区

前端码农


对于一些复杂的页面,往往可以添加功能引导蒙层帮助用户快速上手功能。如下图:

原理

显然,我们需要获取到操作按钮在页面当中的位置坐标及大小,然后在对应位置上方设置一个透明可视区域,这个可以利用css box-shadow属性来创建阴影蒙层。接着,对于有多个步骤引导的,在切换下一个时为了不那么生硬,需要添加动画过渡效果,这里可以借助微信小程序的animate API实现。

封装组件

一个功能页当中的代码,应该尽可能只保留该页面功能业务相关逻辑代码,对于用户引导这种与业务无关的代码应该抽离到外部组件当中,下面就来带大家如何封装实现。

可以看到,一个提示引导有两部分组成:操作区域+操作提示。通常设计,操作提示往往是一张图片素材,但是这里为了简化代码,改为纯文字描述,在理解了原理之后大家可以自己在组件增加属性配置即可。

在src->components目录下,新建user-guide.vue组件,组件代码如下:

<template>
  <view v-if="show" class="user-guide">
    <view id="visual-view" class="visual-view">
      <view class="tip">{{ currentTip.tip }}</view>
    </view>

    <view class="btn-list">
      <button class="btn" @click="close">知道了</button>
      <button v-if="!isEnd" class="btn next" @click="moveView">下一步</button>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref, getCurrentInstance, onUpdated } from 'vue'

interface IProps {
  /** 是否显示 */
  show: boolean
  /** 提示信息列表 */
  list: TipItem[]
}

interface TipItem {
  /** 操作区域位置坐标及大小 */
  width: number
  height: number
  top: number
  left: number
  /** 操作提示内容 */
  tip: string
}

const props = defineProps<IProps>()
const emit = defineEmits(['update:show'])
const step = ref(0)
const isEnd = computed(() => step.value === props.list.length - 1)

let isFirstUpdate = false
onUpdated(() => {
  if (!isFirstUpdate) {
    // 初始化第一个提示
    currentTip.value = props.list[0]
    isFirstUpdate = true
  }
})

// 关闭提示
function close() {
  emit('update:show', false)
}

// @ts-ignore
const { ctx } = getCurrentInstance()
// 给个初始值,否则template中渲染对象报错
const currentTip = ref<TipItem>({
  width: 0,
  height: 0,
  top: 0,
  left: 0,
  tip: '',
})

// 切换下一个
function moveView() {
  const preTip = currentTip.value
  step.value += 1
  currentTip.value = props.list[step.value]
  const nextTip = currentTip.value
  ctx.$scope.animate(
    '#visual-view',
    [
      {
        top: `${preTip.top}px`,
        left: `${preTip.left}px`,
        width: `${preTip.width}px`,
        height: `${preTip.height}px`,
      },
      {
        top: `${nextTip.top}px`,
        left: `${nextTip.left}px`,
        width: `${nextTip.width}px`,
        height: `${nextTip.height}px`,
      },
    ],
    300, // 动画过渡时间
    () => {
      // 调用 animate API 后会在节点上新增一些样式属性覆盖掉原有的对应样式,在动画结束后需要清除增加的属性
      ctx.$scope.clearAnimation()
    },
  )
}
</script>

<style lang="scss">
.user-guide {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  z-index: 9;
  .visual-view {
    position: absolute;
    width: 200rpx;
    height: 100rpx;
    background: transparent;
    border-radius: 10px;
    box-shadow: 0 0 0 1999px rgba(0, 0, 0, 0.55);
    z-index: 10;
    .tip {
      position: absolute;
      z-index: 11;
      bottom: -30px;
      color: #fff;
    }
  }

  .guide-btn {
    position: absolute;
    z-index: 11;
    bottom: 200px;
  }
}
.btn-list {
  position: fixed;
  bottom: 100px;
  display: flex;
  width: 100%;
  justify-content: center;
  z-index: 11;
  .btn {
    margin: 0;
    &.next {
      color: #5080ff;
      margin-left: 10px;
    }
  }
}
</style>

需要说明的是,vue3 compositon写法中是没有this写法的,如果我们要获取挂载在this上面的API,可以通过

import { getCurrentInstance } from 'vue'
const { ctx } = getCurrentInstance()

拿到。而uniapp 小程序端this属性或方法是挂载到ctx下的$scope属性上,所以moveView方法中才有了如下写法:

  ctx.$scope.animate()

页面中使用组件

<template>
    <view id="tip-one" >点击区域一</view>
    <view id="tip-two" >点击区域二</view>
    <view id="tip-three" >点击区域三</view>

    <UserGuide v-model:show="showGuide" :list="guideList" />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import UserGuide from '@/components/user-guide.vue'

  interface TipItem {
    /** 操作区域位置坐标及大小 */
    width: number
    height: number
    top: number
    left: number
    /** 操作提示内容 */
    tip: string
  }

  const showGuide = ref(true)
  const guideList = ref<TipItem[]>([])

  // 假设我们页面中需要提示三个操作区域,提示内容如下:
  const guideTips = ['点击这里赚积分', '点击这里得礼品', '点击分享赚...']
  
  // 获取点击区域在页面中的坐标信息
  const query = uni.createSelectorQuery()
  query.select('#tip-one').boundingClientRect()
  query.select('#tip-two').boundingClientRect()
  query.select('#tip-three').boundingClientRect()
  query.exec((res) => {
    guideList.value = res.map((item: TipItem, index: number) => {
      item.tip = guideTips[index]
      return item
    })
  })
</script>

值得一提的是,在vue3中,已经移除了.sync修饰符,改为v-model:propName写法,如上面代码中v-model:show

运行效果:

总结

本文实现了用户指引蒙层组件的封装,介绍了uniapp中如何使用微信小程序挂载在this下的动画方法api及如何获取页面元素坐标位置信息大小,大家理解原理后可以根据自己的需求改造增加组件配置属性即可。


阅读量:2026

点赞量:0

收藏量:0