您的当前位置:首页正文

vue 指令之气泡提示效果的实现代码

2020-11-27 来源:汇意旅游网

菜鸟学习之路

//L6zt github

自己 在造组件轮子,也就是瞎搞。

自己写了个slider组件,想加个气泡提示。为了复用和省事特此写了个指令来解决。

预览地址

项目地址 github 我叫给它胡博

效果图片

我对指令的理解: 前不久看过 一部分vnode实现源码,奈何资质有限...看不懂。

vnode的生命周期-----> 生成前、生成后、生成真正dom、更新 vnode、更新dom 、 销毁。

而Vue的指令则是依赖于vnode 的生命周期, 无非也是有以上类似的钩子。

代码效果

指令挂A元素上,默认生成一个气泡容器B插入到 body 里面,B 会获取 元素 A 的位置信息 和 自己的
大小信息,经过 一些列的运算,B 元素会定位到 A 的 中间 上 位置。 当鼠标放到 A 上 B 就会显示出来,离开就会消失。

以下代码

气泡指令

import { on , off , once, contains, elemOffset, position, addClass, removeClass } from '../utils/dom';
import Vue from 'vue'
const global = window;
const doc = global.document;
const top = 15;
export default {
 name : 'jc-tips' ,
 bind (el , binding , vnode) {
 // 确定el 已经在页面里 为了获取el 位置信信 
 Vue.nextTick(() => {
 const { context } = vnode;
 const { expression } = binding;
 // 气泡元素根结点
 const fWarpElm = doc.createElement('div');
 // handleFn 气泡里的子元素(自定义)
 const handleFn = binding.expression && context[expression] || (() => '');
 const createElm = handleFn();
 fWarpElm.className = 'hide jc-tips-warp';
 fWarpElm.appendChild(createElm);
 doc.body.appendChild(fWarpElm);
 // 给el 绑定元素待其他操作用
 el._tipElm = fWarpElm;
 el._createElm = createElm;
 // 鼠标放上去的 回调函数
 el._tip_hover_fn = function(e) {
 // 删除节点函数
 removeClass(fWarpElm, 'hide');
 fWarpElm.style.opacity = 0;
 // 不加延迟 fWarpElm的大小信息 (元素大小是 0 0)---> 删除 class 不是立即渲染
 setTimeout(() => {
 const offset = elemOffset(fWarpElm);
 const location = position(el);
 fWarpElm.style.cssText = `left: ${location.left - offset.width / 2}px; top: ${location.top - top - offset.height}px;`;
 fWarpElm.style.opacity = 1;
 }, 16);
 };
 //鼠标离开 元素 隐藏 气泡
 const handleLeave = function (e) {
 fWarpElm.style.opacity = 0;
 // transitionEnd 不太好应该加入兼容
 once({
 el,
 type: 'transitionEnd',
 fn: function() {
 console.log('hide');
 addClass(fWarpElm, 'hide');
 }
 })
 };
 el._tip_leave_fn = handleLeave;
 // 解决 slider 移动结束 提示不消失
 el._tip_mouse_up_fn = function (e) {
 const target = e.target;
 console.log(target);
 if (!contains(fWarpElm, target) && el !== target) {
 handleLeave(e)
 }
 };
 on({
 el,
 type: 'mouseenter',
 fn: el._tip_hover_fn
 });
 on({
 el,
 type: 'mouseleave',
 fn: el._tip_leave_fn
 });
 on({
 el: doc.body,
 type: 'mouseup',
 fn: el._tip_mouse_up_fn
 })
 });
 } ,
 // 气泡的数据变化 依赖于 context[expression] 返回的值
 componentUpdated(el , binding , vnode) {
 const { context } = vnode;
 const { expression } = binding;
 const handleFn = expression && context[expression] || (() => '');
 Vue.nextTick( () => {
 const createNode = handleFn();
 const fWarpElm = el._tipElm;
 if (fWarpElm) {
 fWarpElm.replaceChild(createNode, el._createElm);
 el._createElm = createNode;
 const offset = elemOffset(fWarpElm);
 const location = position(el);
 fWarpElm.style.cssText = `left: ${location.left - offset.width / 2}px; top: ${location.top - top - offset.height}px;`;
 }
 })
 },
 // 删除 事件
 unbind (el , bind , vnode) {
 off({
 el: dov.body,
 type: 'mouseup',
 fn: el._tip_mouse_up_fn
 });
 el = null;
 }
}

slider 组件

<template>
 <div class="jc-slider-cmp">
 <section
 class="slider-active-bg"
 :style="{width: `${left}px`}"
 >
 </section>
 <i
 class="jc-slider-dot"
 :style="{left: `${left}px`}"
 ref="dot"
 @mousedown="moveStart"
 v-jc-tips="createNode"
 >
 </i>
 </div>
</template>
<script>
 import {elemOffset, on, off, once} from "../../utils/dom";
 const global = window;
 const doc = global.document;
 export default {
 props: {
 step: {
 type: [Number],
 default: 0
 },
 rangeEnd: {
 type: [Number],
 required: true
 },
 value: {
 type: [Number],
 required: true
 },
 minValue: {
 type: [Number],
 required: true
 },
 maxValue: {
 type: [Number],
 required: true
 }
 },
 data () {
 return {
 startX: null,
 width: null,
 curValue: 0,
 curStep: 0,
 left: 0,
 tempLeft: 0
 }
 },
 computed: {
 wTov () {
 let step = this.step;
 let width = this.width;
 let rangeEnd = this.rangeEnd;
 if (width) {
 if (step) {
 return width / (rangeEnd / step)
 }
 return width / rangeEnd
 }
 return null
 },
 postValue () {
 let value = null;
 if (this.step) {
 value = this.step * this.curStep;
 } else {
 value = this.left / this.wTov;
 }
 return value;
 }
 },
 watch: {
 value: {
 handler (value) {
 this.$nextTick(() => {
 let step = this.step;
 let wTov = this.wTov;
 if (step) {
 this.left = value / step * wTov;
 } else {
 this.left = value * wTov;
 }
 })
 },
 immediate: true
 }
 },
 methods: {
 moveStart (e) {
 e.preventDefault();
 const body = window.document.body;
 const _this = this;
 this.startX = e.pageX;
 this.tempLeft = this.left;
 on({
 el: body,
 type: 'mousemove',
 fn: this.moving
 });
 once({
 el: body,
 type: 'mouseup',
 fn: function() {
 console.log('end');
 _this.$emit('input', _this.postValue);
 off({
 el: body,
 type: 'mousemove',
 fn: _this.moving
 })
 }
 })
 },
 moving(e) {
 let curX = e.pageX;
 let step = this.step;
 let rangeEnd = this.rangeEnd;
 let width = this.width;
 let tempLeft = this.tempLeft;
 let startX = this.startX;
 let wTov = this.wTov;
 if (step !== 0) {
 let all = parseInt(rangeEnd / step);
 let curStep = (tempLeft + curX - startX) / wTov;
 curStep > all && (curStep = all);
 curStep < 0 && (curStep = 0);
 curStep = Math.round(curStep);
 this.curStep = curStep;
 this.left = curStep * wTov;
 } else {
 let left = tempLeft + curX - startX;
 left < 0 && (left = 0);
 left > width && (left = width);
 this.left = left;
 }
 },
 createNode () {
 const fElem = document.createElement('i');
 const textNode = document.createTextNode(this.postValue.toFixed(2));
 fElem.appendChild(textNode);
 return fElem;
 }
 },
 mounted () {
 this.width = elemOffset(this.$el).width;
 }
 };
</script>
<style lang="scss">
 .jc-slider-cmp {
 position: relative;
 display: inline-block;
 width: 100%;
 border-radius: 4px;
 height: 8px;
 background: #ccc;
 .jc-slider-dot {
 position: absolute;
 display: inline-block;
 width: 15px;
 height: 15px;
 border-radius: 50%;
 left: 0;
 top: 50%;
 transform: translate(-50%, -50%);
 background: #333;
 cursor: pointer;
 }
 .slider-active-bg {
 position: relative;
 height: 100%;
 border-radius: 4px;
 background: red;
 }
 }
</style>
../utils/dom
const global = window;
export const on = ({el, type, fn}) => {
 if (typeof global) {
 if (global.addEventListener) {
 el.addEventListener(type, fn, false)
 } else {
 el.attachEvent(`on${type}`, fn)
 }
 }
 };
 export const off = ({el, type, fn}) => {
 if (typeof global) {
 if (global.removeEventListener) {
 el.removeEventListener(type, fn)
 } else {
 el.detachEvent(`on${type}`, fn)
 }
 }
 };
 export const once = ({el, type, fn}) => {
 const hyFn = (event) => {
 try {
 fn(event)
 }
 finally {
 off({el, type, fn: hyFn})
 }
 }
 on({el, type, fn: hyFn})
 };
 // 最后一个
 export const fbTwice = ({fn, time = 300}) => {
 let [cTime, k] = [null, null]
 // 获取当前时间
 const getTime = () => new Date().getTime()
 // 混合函数
 const hyFn = () => {
 const ags = argments
 return () => {
 clearTimeout(k)
 k = cTime = null
 fn(...ags)
 }
 };
 return () => {
 if (cTime == null) {
 k = setTimeout(hyFn(...arguments), time)
 cTime = getTime()
 } else {
 if ( getTime() - cTime < 0) {
 // 清除之前的函数堆 ---- 重新记录
 clearTimeout(k)
 k = null
 cTime = getTime()
 k = setTimeout(hyFn(...arguments), time)
 }
 }}
 };
 export const contains = function(parentNode, childNode) {
 if (parentNode.contains) {
 return parentNode !== childNode && parentNode.contains(childNode)
 } else {
 // https://developer.mozilla.org/zh-CN/docs/Web/API/Node/compareDocumentPosition
 return (parentNode.compareDocumentPosition(childNode) === 16)
 }
 };
 export const addClass = function (el, className) {
 if (typeof el !== "object") {
 return null
 }
 let classList = el['className']
 classList = classList === '' ? [] : classList.split(/\s+/)
 if (classList.indexOf(className) === -1) {
 classList.push(className)
 el.className = classList.join(' ')
 }
 };
 export const removeClass = function (el, className) {
 let classList = el['className']
 classList = classList === '' ? [] : classList.split(/\s+/)
 classList = classList.filter(item => {
 return item !== className
 })
 el.className = classList.join(' ')
 };
 export const delay = ({fn, time}) => {
 let oT = null
 let k = null
 return () => {
 // 当前时间
 let cT = new Date().getTime()
 const fixFn = () => {
 k = oT = null
 fn()
 }
 if (k === null) {
 oT = cT
 k = setTimeout(fixFn, time)
 return
 }
 if (cT - oT < time) {
 oT = cT
 clearTimeout(k)
 k = setTimeout(fixFn, time)
 }
 }
 };
 export const position = (son, parent = global.document.body) => {
 let top = 0;
 let left = 0;
 let offsetParent = son;
 while (offsetParent !== parent) {
 let dx = offsetParent.offsetLeft;
 let dy = offsetParent.offsetTop;
 let old = offsetParent;
 if (dx === null) {
 return {
 flag: false
 }
 }
 left += dx;
 top += dy;
 offsetParent = offsetParent.offsetParent;
 if (offsetParent === null && old !== global.document.body) {
 return {
 flag: false
 }
 }
 }
 return {
 flag: true,
 top,
 left
 }
 };
 export const getElem = (filter) => {
 return Array.from(global.document.querySelectorAll(filter));
 };
 export const elemOffset = (elem) => {
 return {
 width: elem.offsetWidth,
 height: elem.offsetHeight
 }
 };

总结

以上所述是小编给大家介绍的vue 指令之气泡提示效果的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

显示全文