vue3-H5实现Tour指引组件

454 阅读3分钟

前言

实现效果
由引导线指向位置,框内自定义内容及下一步按钮。点击下一步自动指向下一提示位置,可以设置提示框上下左右位置。
实现思路
通过ref确定指向位置,使用slot插槽自定义提示内容,修改样式调整提示框位置。

效果图

使用主要方法&遇到问题

问题:目标元素超出可视窗口时显示问题
// 指定dom元素滚动到可视窗口
const scrollDom = (targetDom) => {
  console.log('指定dom元素滚动到可视窗口',targetDom);
  targetDom?.scrollIntoView({ block: 'nearest' }) 
  // 通过scrollIntoView方法滚动到可视窗口中间(block的值:start、center、end、nearest)
}
问题:tour组件出现后,页面还可以滑动

目标:tour出现后,固定页面

解决:tour出现后设置页面不可滚动,tour结束后设置页面可以滚动

// 禁止页面滚动:(initStepsListFn)
document.body.style.overflow='hidden'

// 允许页面滚动:(closeFn)
document.body.style.overflow='visible'
问题:tour出现后页面不可点击
    // 禁止页面点击事件
    let getId = document.getElementById('items');
    getId.style.pointerEvents='none';

    // 允许页面点击事件
    let getId = document.getElementById('items');
    getId.style.pointerEvents='auto'
问题:ref获取不到dom问题:

情况1:ref获取dom为undefined

情况2:ref获取dom不为undefined,但没有拿到dom

<div ref="stepRef1" >测试tour</div>
<TourGuidelines :stepsList="stepsList" :key="stepsList" :show="show" >

<script setup>
let stepRef1 = ref(null);
let stepsList = ref([]);

const initStepsListFn = ()=>{
    stepsList.value = [
    {
      step: 1,
      content: '测试Tour',
      target: stepRef1.value,
    },
  ]
}
// 在onMounted中进行赋值:在dom渲染完成后进行赋值ref,否则拿不到dom元素
onMounted(() => {
  initStepsListFn()
  // 如果ref组件中存在if会导致获取不到ref,使用nextTick保证dom更新完毕后再执行
 //  nextTick(() => initStepsListFn())
    })
</script>
// 普通标签可以直接获取dom节点元素
// 组件不能直接获取到dom节点元素,需要在$el中拿到,再从对应children方法中找到具体标签

<van-field label="绑定ref" placeholder="请选择"  ref="stepRef1" />
<TourGuidelines :stepsList="stepsList" :key="stepsList" :show="show" >

<script setup>
let stepRef1 = ref(null);
let stepsList = ref([]);

const initStepsListFn = ()=>{
    stepsList.value = [
    {
      step: 1,
      content: '测试Tour',
      target: stepRef1.value?.$el?.children?.[1]
    },
  ]
}
// 在onMounted中进行赋值:在dom渲染完成后进行赋值ref,否则拿不到dom元素
onMounted(() => {
  nextTick(() => initStepsListFn())
  })
</script>
问题:vue3中ref绑定自定义组件没有获取到dom

解决方法:使用vue3的defineExpose将子组件的属性暴露出去

# 子组件代码片段
<div ref="childrenDom">子组件</div>
<script setup>
    import {ref} from "vue";

    const childrenDom = ref();

    defineExpose({
        childrenDom
    });
</script>
#父组件代码片段
<Children ref="child"/>

<script setup>
    import Children from "./Children.vue";

    const child = ref(null);
    
onMounted(() => {
	console.log(child.value)
    })

</script>
问题:在子组件中暴露ref dom元素时,父组件依旧拿不到

原因:ref写在了v-if 中

解决:在外层写一个div,将ref下载外层div中

#父组件代码片段
<div  ref="child">
	<Children/>
</div>

<script setup>
    const child = ref(null);
    
onMounted(() => {
	console.log(child.value)
    })

</script>
问题:绑定的ref在v-if中时,无法获取到dom

原因:ref获取dom时,在v-if中当为false时,此时dom未被渲染,无法获取到该元素

解决方法:在v-if标签外层添加一个div标签,并给div标签绑定ref即可。获取子组件dom必须在dom渲染后才可以获取到

问题:组件在手机上不显示背景色

原因:css设置颜色为十六进制颜色,故不显示

<template>
	<div ref="testTopRef">测试0</div>
    <van-row style="margin-top: 10rem;">
      <van-col span="6">
        <div ref="testBottomRef">
          <div>测试1</div>
        </div>
      </van-col>
      <van-col span="6" >
        <div class="fonts1" ref="testBottomRef2">测试2</div>
      </van-col>
      <van-col span="6">
        <div  ref="testBottomRef3">测试3</div>
      </van-col>
      <van-col span="6">
        <div class="fonts1" ref="testBottomRef4">测试4</div>
      </van-col>
      <van-col span="6" style="padding-top:10rem">
        <div ref="testScrollRef"> 测试滚动</div>
      </van-col>
      <van-col span="24" style="padding-top:10rem">
        <div > 底部</div>
      </van-col>
    </van-row>

		<TourGuidelines :stepsList="stepsList" :key="stepsList" :show="show" @change="changeFn" @close="closeFn">
      <!-- <div>
        <span style="color: aqua;">插槽-公共内容</span>
      </div> -->
    </TourGuidelines>
</template>

<script setup>
import { nextTick,onMounted, ref } from 'vue';
import TourGuidelines from '../../components/TourGuidelines/index.vue';

    // let show = ref(true)
    let show = ref(!localStorage.getItem('showTour'))  // 显示tour

    let testScrollRef = ref(null)
    let testTopRef = ref(null)
    let testBottomRef = ref(null)
    let testBottomRef3 = ref(null)
  
    const stepsList = ref([])

    const changeFn = (value) => {
      console.log('change', value);
    }
    const closeFn = (value) => {
      console.log('closeFn', value);
      show.value = value;
      // 允许页面滚动:
      document.body.style.overflow='visible'
      // 不显示tour - 当仅显示一次时需要本地存储
      localStorage.setItem('showTour',true);
    }

    // tour初始赋值
    const initStepsListFn = ()=>{
        // 禁止页面滚动:
        document.body.style.overflow='hidden';
        nextTick(()=>{
              stepsList.value = [
              {
                step: 1,
                content: '文字内容',
                buttonContent: '下一步?',
                target: testTopRef.value,
                location: 'bottom',
                cover: (
                  `<div style="color:blue;">啊
                    <div>换行</div>
                    <div> <span style="color:red;">自定义内容 - 标签</span></div>
                    嗷</div>`
                ),
              },
              {
                step: 2,
                content: '222222',
                target: testBottomRef.value,
                location: 'top',
                cover: '自定义内容 - 純字符串left',
                lineLocation: 'right',
              },
              {
                step: 3,
                content: '333333333',
                buttonContent: '下一步',
                target: testBottomRef3.value,
                lineLocation: 'left',
                cover: (
                  `<span style="color:red;">自定义内容-模板字符串right</span>`
                ),
              },
              {
                step: 4,
                content: '测试滚动',
                buttonContent: '知道了',
                target: testScrollRef.value,
                location: 'top',

              },
            ]
            })
    }

    onMounted(() => {
      show.value&&initStepsListFn()
    })
  
</script>

使用中bug

适配问题:Android无问题,在ios系统中个别Tour组件是出现位置不准确问题

Android ios

原因:当target值为ref获取值下的子节点时,在ios系统中会发生位置错乱,如下图stepRef2.value?.[0]?.$el会发生错乱

ios版本低的不兼容,版本高的不会发生错乱