网站上这个代码排版我真的是醉了,附上github地址,直接跑起来看效果吧~
前言
大佬们,都是手摸手带你们学习,而我就摸不了你们的小手了。因为可能我分享的内容,是一些大家早已知悉的内容,只不过这个需求,挺有意思的,我实现完之后。写这个文章为了分享一些能帮助到别人的内容,也是为了归档一下自己的知识点。有写的不好的地方望指正。
需求
大家在浏览电商的 app 的时候,大多数在详情页的右下角上方,会有一个悬浮的按钮。用来分享商品详情、或者回到首页等功能的实现。我们的项目呢是,基于小程序的一款金融平台,应用了小程序原声+webview 的方案。因为 webview 里面路由跳转是 vue 的跳转,小程序并没有 navigateto,在不同系统,机型上,小程序的 webview 实现方式有所不同,而且页面层级增加之后。就会导致用户回退的时候,可能会点很多次才能回到小程序的原生页面。所以我们也采用了这一方案。
实现
组件好多的内容,很多大神都有文章来写的用法概念什么,我就不一一赘述了。首先这种组件在我们项目里面是 webview 里面所有页面都要用的,肯定得是一个全局组件,如果一个个引入那真滴是太 low 了。我就直接给大家上代码了。
vue 组件
<template> <div class="yht-float-btn pos-r" v-show="showBtn" :style="{'width':itemWidth+'px','height':itemHeight+'px','left':left+'px','top':top-70+'px'}" ref="floatDiv"> <div class="top-icon pos-a"> <transition name="slide-fade"> <div class="float-box text-center hide-box" v-show="show" @click="handleClickItem(0)"> <!-- 问题 --> <img src="@/assets/img/yht/icon_question.png" alt=""> </div> </transition> <transition name="slide-fade"> <div class="float-box text-center hide-box" v-show="show" @click="handleClickItem(1)"> <img src="@/assets/img/yht/icon_home.png" alt=""> <!-- 首页 --> </div> </transition> </div> <div class="float-box text-center pos-a bottom-icon" @click="handleClickMenu"> <div> <img v-if="show" src="@/assets/img/yht/icon_close.png" alt=""> <img v-else src="@/assets/img/yht/icon_shortcut.png" alt=""> </div> </div> </div></template><script>import Util from "@/utils/common";import router from "../../router";export default { name: "drag", components: {}, props: { text: { type: String, default: "默认文字" }, itemWidth: { type: Number, default: 66 }, itemHeight: { type: Number, default: 66 }, gapWidth: { type: Number, default: 10 }, coefficientHeight: { type: Number, default: 0.8 }, showBtn: { // 是否显示此toast default: false } }, data() { return { timer: null, currentTop: 0, clientWidth: 0, clientHeight: 0, left: 0, top: 0, show: false }; }, created() { this.clientWidth = document.documentElement.clientWidth; this.clientHeight = document.documentElement.clientHeight; this.left = this.clientWidth - this.itemWidth - this.gapWidth; this.top = this.clientHeight * this.coefficientHeight; }, mounted() { window.addEventListener("scroll", this.handleScrollStart); this.$nextTick(() => { const div = this.$refs.floatDiv; div.addEventListener("touchstart", e => { div.style.transition = "none"; }); div.addEventListener("touchmove", e => { e.preventDefault(); if (e.targetTouches.length === 1) { let touch = event.targetTouches[0]; this.left = touch.clientX - this.itemWidth / 2; this.top = touch.clientY - this.itemHeight / 2; } }); div.addEventListener("touchend", e => { div.style.transition = "all 0.3s"; if (this.left > this.clientWidth / 2) { // 浮动靠右边 this.left = this.clientWidth - this.itemWidth - this.gapWidth; console.log("这里是右边"); } else { // 靠左边 this.left = this.gapWidth; console.log("这里是左边"); } if (this.top < 0) { // 防止滑到上方被隐藏 this.top = this.clientHeight * this.coefficientHeight; } }); }); }, beforeDestroy() { window.removeEventListener("scroll", this.handleScrollStart); }, methods: { handleScrollStart() { this.timer && clearTimeout(this.timer); this.timer = setTimeout(() => { this.handleScrollEnd(); }, 300); this.currentTop = document.documentElement.scrollTop || document.body.scrollTop; if (this.left > this.clientWidth / 2) { this.left = this.clientWidth - this.itemWidth / 2; } else { this.left = -this.itemWidth / 2; } }, handleScrollEnd() { let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop === this.currentTop) { if (this.left > this.clientWidth / 2) { this.left = this.clientWidth - this.itemWidth - this.gapWidth; } else { this.left = this.gapWidth; } clearTimeout(this.timer); } }, handleClickMenu() { this.show = !this.show; }, handleClickItem(type) { if (type === 0) { router.push({ path: "/question/question" }); } else { if (window.sessionStorage.getItem("isMiniEnvironment") !== "0") { Util.toMiniIndex(); // 这里是回到小程序首页 } else { router.replace({ path: "/yht/index" }); } } } }};</script><style lang="less" scoped>.yht-float-btn { z-index: 99999; transition: all 0.3s; position: fixed; bottom: 20vw; img { width: 66px; height: 66px; object-fit: contain; margin-bottom: 3px; } p { font-size: 7px; }}.float-box { width: 66px; height: 66px;}.slide-fade-enter-active { transition: all 1s ease;}.slide-fade-leave-active { transition: all 0.8s cubic-bezier(1, 0.5, 0.8, 1);}.slide-fade-enter, .slide-fade-leave-to/* .slide-fade-leave-active for below version 2.1.8 */ { transform: translateY(10px); opacity: 0;}.top-icon { left: 0; bottom: 66px;}.bottom-icon { bottom: 0; left: 0;}</style>
这个组件写好之后 可以先 引入到一个页面里面调一下样式。
实现动态加载组件
import Float from './floatBtn.vue';let dom;const FloatBtn = {}; // 定义插件对象FloatBtn.install = function(Vue, options) { // vue的install方法,用于定义vue插件 // 如果toast还在,则不再执行 if (document.getElementsByClassName('yht-float-btn').length) { return; } let Instance = Vue.extend(Float); const initInstance = () => { // 实例化vue实例 dom = new Instance(); dom.vm = dom.$mount(); let boxEl = dom.$mount().$el; document.body.appendChild(boxEl); }; Vue.prototype.$floatBtn = { showFloat(options) { if (!dom) { initInstance(); } dom.vm.showBtn = true; // 显示toast }, hideFloat(options = 0) { if (!dom) { initInstance(); } dom.vm.showBtn = false; // 显示整体 } };};export default FloatBtn;
在全局中的使用
需求是,当用户进入页面栈两层之后才给用户显示这个浮动组件。 所以在路由守卫中先定义了一个计数器,在路由跳转的时候计数器增加。其实本来是想在路由守卫中来调用这个浮动组件的,但是笔者在路由守卫中,尝试调用组件的时候,发现访问不到这个组件。因为当时需求比较着急,就换了一种方式。也希望有大佬能指点一下这块的知识点~
let count = 2; // 悬浮按钮计数器count++;window.sessionStorage.setItem('floatCount', count);// 然后在router-view页面里this.floatCount = this.$storage.get('floatCount'); // 获取悬浮按钮计数器if (this.floatCount > 3) { this.$floatBtn.showFloat();}// 当然如果没有特别的需求的话,这个组件就可以在任意一个vue页面里面去调用 this.$floatBtn.showFloat() 这个方法。
// 还要在mainjs里面import FloatBtn from '@/components/floatBtn/floatBtn';Vue.use(FloatBtn); // 增加 悬浮按钮
效果如下图:
到这里就陪你写完了一个vue全局的组件,奖励自己一朵小发发~