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


Posted in Javascript onOctober 18, 2018

菜鸟学习之路

//L6zt github

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

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

预览地址

项目地址 github 我叫给它胡博

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

效果图片

我对指令的理解: 前不久看过 一部分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 指令之气泡提示效果的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

Javascript 相关文章推荐
仿服务器端脚本方式的JS模板实现方法
Apr 27 Javascript
jquery tools 系列 scrollable(2)
Sep 06 Javascript
基于JQuery的密码强度验证代码
Mar 01 Javascript
js显示文本框提示文字的方法
May 07 Javascript
mvc 、bootstrap 结合分布式图简单实现分页
Oct 10 Javascript
js微信扫描二维码登录网站技术原理
Dec 01 Javascript
关于vue-resource报错450的解决方案
Jul 24 Javascript
jQuery实现可兼容IE6的遮罩功能详解
Sep 19 jQuery
iview给radio按钮组件加点击事件的实例
Sep 30 Javascript
Vue2.0 给Tab标签页和页面切换过渡添加样式的方法
Mar 13 Javascript
Vue 路由 过渡动效 数据获取方法
Jul 31 Javascript
js实现验证码干扰(动态)
Feb 23 Javascript
使用JS获取页面上的所有标签
Oct 18 #Javascript
深入理解 Koa 框架中间件原理
Oct 18 #Javascript
JS实现十分钟倒计时代码实例
Oct 18 #Javascript
基于Vue实现可以拖拽的树形表格实例详解
Oct 18 #Javascript
JavaScript的词法结构精华篇
Oct 17 #Javascript
Javascript中parseInt的正确使用方式
Oct 17 #Javascript
教你如何编写Vue.js的单元测试的方法
Oct 17 #Javascript
You might like
一个php作的文本留言本的例子(三)
2006/10/09 PHP
简体中文转换为繁体中文的PHP函数
2006/10/09 PHP
php str_pad() 将字符串填充成指定长度的字符串
2010/02/23 PHP
PHP写的加密函数,支持私人密钥(详细介绍)
2013/06/09 PHP
php使用指定字符列表生成随机字符串的方法
2015/04/18 PHP
PHP创建多级目录的两种方法
2016/10/28 PHP
Javascript实现的简单右键菜单类
2015/09/23 Javascript
js基于cookie方式记住返回页面用法示例
2016/05/27 Javascript
JS实现图片的不间断连续滚动的简单实例
2016/06/03 Javascript
Javascript在IE和Firefox浏览器常见兼容性问题总结
2016/08/03 Javascript
Vue 仿百度搜索功能实现代码
2017/02/16 Javascript
ES6新特性四:变量的解构赋值实例
2017/04/21 Javascript
JavaScript轮播停留效果的实现思路
2018/05/24 Javascript
JavaScript ES6中的简写语法总结与使用技巧
2018/12/30 Javascript
微信小程序调用天气接口并且渲染在页面过程详解
2019/06/24 Javascript
Vue.js实现tab切换效果
2019/07/24 Javascript
javascript中闭包closure的深入讲解
2021/03/03 Javascript
详解使用 pyenv 管理多个版本 python 环境
2017/10/19 Python
python Selenium实现付费音乐批量下载的实现方法
2019/01/24 Python
python对绑定事件的鼠标、按键的判断实例
2019/07/17 Python
python requests使用socks5的例子
2019/07/25 Python
基于python全局设置id 自动化测试元素定位过程解析
2019/09/04 Python
Python爬虫库requests获取响应内容、响应状态码、响应头
2020/01/25 Python
Python如何把十进制数转换成ip地址
2020/05/25 Python
python模块内置属性概念及实例
2021/02/18 Python
解决margin 外边距合并问题
2019/07/03 HTML / CSS
世界上最大的艺术社区:SAA
2020/12/30 全球购物
如何提高JDBC的性能
2013/04/30 面试题
网络工程专业毕业生推荐信
2013/10/28 职场文书
五年级音乐教学反思
2014/02/06 职场文书
在校大学生个人的自我评价
2014/02/13 职场文书
竞选体育委员演讲稿
2014/04/26 职场文书
合同意向书范本
2014/07/30 职场文书
微观世界观后感
2015/06/10 职场文书
2019个人工作总结
2019/06/21 职场文书
Python捕获、播放和保存摄像头视频并提高视频清晰度和对比度
2022/04/14 Python