zoukankan      html  css  js  c++  java
  • vue 指令---气泡提示(手撸实战)


    菜鸟学习之路
    //L6zt github
    自己在造组件轮子,也就是瞎搞。
    自己写了个slider组件,想加个气泡提示。为了复用和省事特此写了个指令来解决。
    预览地址
    项目地址 github 我叫给它胡博

    css图形制作

    我对指令的理解: 前不久看过 一部分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="{ `${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,
             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;
             100%;
            border-radius: 4px;
            height: 8px;
            background: #ccc;
            .jc-slider-dot {
                position: absolute;
                display: inline-block;
                 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 {
                 elem.offsetWidth,
                height: elem.offsetHeight
            }
        };
    
    

    原文地址: https://segmentfault.com/a/1190000016719995

  • 相关阅读:
    17、静态链表
    16、约瑟夫问题
    15、循环链表
    9、插入排序
    14、企业链表
    13、单向链表
    12、顺序表的顺序存储结构
    11、归并排序
    10、快速排序
    原型模式
  • 原文地址:https://www.cnblogs.com/lalalagq/p/9906548.html
Copyright © 2011-2022 走看看