vue 指令之气泡提示效果的实现代码
程序员文章站
2022-06-08 23:26:26
菜鸟学习之路
//l6zt github
自己 在造组件*,也就是瞎搞。
自己写了个slider组件,想加个气泡提示。为了复用和省事特此写了个指令来解决。
项...
菜鸟学习之路
//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 指令之气泡提示效果的实现代码,希望对大家有所帮助