WEB
driver.js
npm install driver.js --save
utils / driver.js
export default [
{
element: '#some-element1',
popover: {
title: '第一项',
description: '第一项的描述',
position: 'bottom',
}
},
{
element: '#some-element2',
popover: {
title: '第二项',
description: '第二项的描述',
position: 'top',
}
},
]
挂载
import Driver from 'driver.js'
import 'driver.js/dist/driver.min.css'
import Vue from 'vue'
Vue.prototype.$driver = new Driver()
import Driver from 'driver.js';
import 'driver.js/dist/driver.min.css'
import Vue from 'vue'
Vue.prototype.$driver = new Driver({
allowClose: false,
doneBtnText: '完成',
closeBtnText: '关闭',
stageBackground: '#fff',
nextBtnText: '下一步',
prevBtnText: '上一步',
})
.vue
<div id="some-element1">第一个</div>
<div id="some-element2">第二个</div>
import driverStep from "@/utils/driver"
this.$driver.defineSteps(driverStep)
this.$driver.start()
start () {
this.$nextTick(() => {
this.$driver.defineSteps(driverStep)
this.$driver.start()
})
}
Vue3.0 compositionAPI
<!--
* @Author: zhang gen yuan
* @Date: 2021-09-17 11:01:54
* @Descripttion:
-->
<template>
<h1 id="div1">1111111111</h1>
<h1 id="div2">2222222222</h1>
</template>
<script setup>
import Driver from "driver.js";
import driverStep from "/@/utils/driver";
import { onMounted, nextTick } from "vue";
import { useRouter } from 'vue-router'
let router = useRouter()
let $driver = new Driver({
allowClose: false,
doneBtnText: "完成",
closeBtnText: "关闭",
stageBackground: "#fff",
nextBtnText: "下一步",
prevBtnText: "上一步",
onHighlightStarted: (Element) => {
},
onHighlighted: (Element) => {
},
onDeselected: (Element) => {
},
onReset: (Element) => {
},
onNext: (Element) => {
},
onPrevious: (Element) => {
},
});
onMounted(() => {
$driver.defineSteps(driverStep);
$driver.start();
});
</script>
<style>
</style>
uniAPP
index.vue
<template>
<view class="content">
<!-- <image class="logo" src="/static/logo.png" id="testid1"></image>
<view class="text-area" id="testid2">
<text class="title">Hello</text>
</view>
<view id="testid3">测试行1</view>
<view id="testid4"><button>测试按钮1</button></view> -->
<view id="testid1">111</view>
<view id="testid2">222</view>
<view id="testid3">333</view>
<view id="testid4">444</view>
<!-- 新手引导 -->
<guide :show="showGuide" :width="cWidth" :height="cHeight" :left="cLeft" :top="cTop"
:showMessage='cShowMsg' :currentIndex="currentIndex" :noticeArray="noticeArray" @click="clicktoNext">
</guide>
</view>
</template>
<script>
import guide from '@/components/guide/guide.vue'
export default {
data() {
return {
showGuide: true,
cShowMsg: '',
cWidth: '',
cHeight: '',
cLeft: '',
cTop: '',
currentIndex: 0,
noticeArray: [{
"showID": "testid1",
"showMessage": "测试文本1",
"type": "left"
},
{
"showID": "testid2",
"showMessage": "测试文本2",
"type": "right"
},
{
"showID": "testid3",
"showMessage": "测试多行文本测试多行文本测试多行文本测试多行文本测试多行文本测试多行文本测试多行文本测试多行文本测试多行文本测试多行文本",
"type": "top"
},
{
"showID": "testid4",
"showMessage": "测试文本4",
"type": "bottom"
}
]
}
},
components: {
guide
},
onLoad() {
let _this = this;
this.$nextTick(function() {
if (_this.currentIndex >= _this.noticeArray.length) {
_this.showGuide = false;
return;
}
_this.showGuide = true;
_this.cShowMsg = _this.noticeArray[_this.currentIndex].showMessage;
var idS = '#' + _this.noticeArray[_this.currentIndex].showID;
console.log(idS)
const query = uni.createSelectorQuery().in(_this);
query.select(idS).boundingClientRect(data => {
console.log("得到布局位置信息" + JSON.stringify(data));
_this.cWidth = data.width;
_this.cHeight = data.height;
_this.cLeft = data.left;
_this.cTop = data.top;
}).exec();
});
},
methods: {
clicktoNext() {
if (this.currentIndex >= this.noticeArray.length) {
this.showGuide = false;
return;
}
this.noticeArray[this.currentIndex].zindex = 0;
this.cShowMsg = '';
this.currentIndex++;
if (this.currentIndex >= this.noticeArray.length) {
this.showGuide = false;
return;
}
this.cShowMsg = this.noticeArray[this.currentIndex].showMessage;
var idS = '#' + this.noticeArray[this.currentIndex].showID;
console.log(idS)
const query = uni.createSelectorQuery().in(this);
query.select(idS).boundingClientRect(data => {
this.cWidth = data.width;
this.cHeight = data.height;
this.cLeft = data.left;
this.cTop = data.top;
}).exec();
},
}
}
</script>
guide.vue
<!-- guide.vue -->
<template>
<view v-show="show" class="main" @touchmove.stop>
<view class="rect-shadow" :style="{'width':width+'px','height':height+'px','left':left+'px',top:top+'px','border-radius': noticeArray[currentIndex].radius?noticeArray[currentIndex].radius+'rpx':'' }"></view>
<template v-if="noticeArray[currentIndex].type == 'left' ">
<view class="show-message_left" :style="{'top': top +'px', 'right': winWidth-right+width+20+'px' }">
<view style="padding: 12rpx 18rpx;">{{showMessage}}</view>
</view>
</template>
<template v-if="noticeArray[currentIndex].type == 'top' ">
<view :class="left<(winWidth/2)?'show-message_top_left':'show-message_top_right'" :style="{'top': (top-height-topMsgHeght) +'px', 'left': left<(winWidth/2)?left+'px':'' , 'right': left>(winWidth/2)?(winWidth-right)+'px':'' }">
<view style="padding: 12rpx 18rpx;">{{showMessage}}</view>
</view>
</template>
<template v-if="noticeArray[currentIndex].type == 'right' ">
<view class="show-message_right" :style="{'top': top +'px', 'left': (left+width+20) +'px' }">
<view style="padding: 12rpx 18rpx;">{{showMessage}}</view>
</view>
</template>
<template v-if="noticeArray[currentIndex].type == 'bottom' ">
<view :class="left<(winWidth/2)?'show-message_bottom_left':'show-message_bottom_right'" :style="{'top': (top+height+15) +'px', 'left': left<(winWidth/2)?left+'px':'' , 'right': left>(winWidth/2)?(winWidth-right) +'px':'' }">
<view style="padding: 12rpx 18rpx;">{{showMessage}}</view>
</view>
</template>
<view class="cover-wrap" >
<view class="guide-next" @tap="click" :style="{color: currentIndex==noticeArray.length-1? '#EA3E4F':'#000000'}">{{nextText}}</view>
</view>
</view>
</template>
<script>
export default {
name: "guide",
props: {
show: {
type: Boolean,
default: false
},
left: {
type: [Number, String],
default: ''
},
right: {
type: [Number, String],
default: ''
},
top: {
type: [Number, String],
default: ''
},
width: {
type: [Number, String],
default: ''
},
height: {
type: [Number, String],
default: ''
},
showMessage: {
type: String,
default: ''
},
currentIndex: {
type: Number,
default: 0
},
noticeArray: {
type: Array,
default: []
}
},
data() {
return {
topMsgHeght: 0,
winWidth: 0,
nextText: '下一步'
}
},
watch: {
show(n) {},
currentIndex(value) {
if (value != this.noticeArray.length&&this.noticeArray[value].type == 'top') {
const className = this.left<(this.winWidth/2) ? '.show-message_top_left' : '.show-message_top_right'
const query = uni.createSelectorQuery().in(this);
query.select(className).boundingClientRect(data => {
this.topMsgHeght = data.height - 29
}).exec();
}
if (value == this.noticeArray.length-1) {
this.nextText = '知道了,关闭说明'
}
}
},
methods: {
click() {
this.$emit('click');
}
},
created() {
setTimeout(() => {
uni.getSystemInfo({
success: (res) => {
this.winWidth = res.windowWidth;
}
});
}, 60)
}
}
</script>
<style lang="scss" scoped>
.main {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 99999;
}
.rect-shadow {
position: absolute;
border-radius: 12rpx;
box-shadow: 0 0 0 3000px rgba(0, 0, 0, 0.5);
pointer-events: none;
}
.show-message_left,
.show-message_top_left,
.show-message_top_right,
.show-message_right,
.show-message_bottom_left,
.show-message_bottom_right {
position: absolute;
border-radius: 12rpx;
background-color: rgba(0,0,0,.5);
text-align: center;
pointer-events: none;
color: #fff;
font-size: 24rpx;
max-width: 50%;
text-align: left;
}
.show-message_left::after {
content: '';
border-left: 12rpx solid rgba(0,0,0,.5);
border-top: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-bottom: 12rpx solid transparent;
width: 0;
height: 0;
position: absolute;
top: 25%;
right: -22rpx;
}
.show-message_top_left::after,.show-message_top_right::after {
content: '';
border-left: 12rpx solid transparent;
border-top: 12rpx solid rgba(0,0,0,.5);
border-right: 12rpx solid transparent;
border-bottom: 12rpx solid transparent;
width: 0;
height: 0;
position: absolute;
top: 100%;
}
.show-message_top_left::after {
left: 20%;
}
.show-message_top_right::after {
right: 20%;
}
.show-message_right::after {
content: '';
border-left: 12rpx solid transparent;
border-top: 12rpx solid transparent;
border-right: 12rpx solid rgba(0,0,0,.5);
border-bottom: 12rpx solid transparent;
width: 0;
height: 0;
position: absolute;
top: 25%;
left: -24rpx;
}
.show-message_bottom_left::after, .show-message_bottom_right::after {
content: '';
border-left: 12rpx solid transparent;
border-top: 12rpx solid transparent;
border-right: 12rpx solid transparent;
border-bottom: 12rpx solid rgba(0,0,0,.5);
width: 0;
height: 0;
position: absolute;
top: -22rpx;
}
.show-message_bottom_left::after {
left: 20%;
}
.show-message_bottom_right::after {
right: 20%;
}
.cover-wrap {
width: 100vw;
height: 100vh;
top: 0;
left: 0;
position: absolute;
.guide-next {
background-color: #FFFFFF;
font-size: 28rpx;
border-radius: 38rpx;
position: absolute;
bottom: 372rpx;
left: 50%;
transform: translate(-50%,0);
padding: 16rpx 32rpx;
}
}
</style>