目前我正在做的这个项目中,有个表格的数据比较多,导致页面加载时间长,体验感大大滴下降了,就在网上各种搜各种Ctrl CV,最后还算是实现了想要的结果把,九成新,微瑕,又不是不能用。


vue自定义指令

众所周知,vue中除了内置的v-ifv-showv-textv-htmlv-model等几个指令之外,还支持注册自定义指令,同时vue中指令的定义对象也拥有和组件相同的生命周期钩子函数,使用起来歪瑞购得。

const myDirective = {
  // 在绑定元素的 attribute 前
  // 或事件监听器应用前调用
  created(el, binding, vnode, prevVnode) {
    // 下面会介绍各个参数的细节
  },
  // 在元素被插入到 DOM 前调用
  beforeMount(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {},
  // 在绑定元素的父组件
  // 及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {},
  // 绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode) {}
}

钩子函数

指令的钩子会传递以下几种参数:

  • el:指令绑定到的元素。这可以用于直接操作 DOM。
  • binding:一个对象,包含以下属性。
    • value:传递给指令的值。例如在 v-my-directive="1 + 1" 中,值是 2
    • oldValue:之前的值,仅在 beforeUpdateupdated 中可用。无论值是否更改,它都可用。
    • arg:传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"
    • modifiers:一个包含修饰符的对象 (如果有的话)。例如在 v-my-directive.foo.bar 中,修饰符对象是 { foo: true, bar: true }
    • instance:使用该指令的组件实例。
    • dir:指令的定义对象。
  • vnode:代表绑定元素的底层 VNode。
  • prevNode:之前的渲染中代表指令所绑定元素的 VNode。仅在 beforeUpdate 和 updated 钩子中可用。

创建自定义指令

为了方便使用,直接将指令注册到全局使用首先在src目录下新创建directive目录,并在directive目录下创建index.js文件和loadmore目录,loadmore目录下在创建index.js文件;

// 刚好还用到防抖函数
import { throttle } from 'lodash-es';

const loadmore = {
  mounted(el, binding) {
    // 因为使用的是elementui,所以查找这个元素,根据实际情况而定
    const ele = el.querySelector('.el-scrollbar__wrap');
    ele.addEventListener(
      'scroll',
      throttle(function () {
        //                                    1200 可根据实际清空调整
        this.scrollHeight - this.scrollTop <= 1200 && binding.value();
      }, 150),
    );
  },
  updated(el, binding) {
    const ele = el.querySelector('.el-scrollbar__wrap');
    if (binding.value?.toTop) {
      ele.scrollTop = 0;
      delete binding.value.toTop;
    }
  },
};
export default loadmore;
// 导入自定义指令
import loadmore from './loadmore';

const directives = {
  loadmore,
};

export default {
  install(app) {
    Object.keys(directives).forEach(key => {
      app.directive(key, directives[key]);
    });
  },
};
...
import Directives from './directive/';
...
const app = createApp(App);
// 注册即可
app.use(Directives);
...

使用

<!-- 在组件上使用 v-loadmore="onLoadMore 使用" -->
<el-table v-loadmore="onLoadMore" :data="displayData">
...
</el-table>

<script>
...
  const onLoadMore = () => {
    let tempLen = unref(displayData).length;
    // 每次触发 push 50 条数据
    tempLen < unref(resultData).length &&
      unref(displayData).push(...unref(searchData).slice(tempLen, tempLen + 50));
  }
  ...
</script>

按钮点击水波纹效果

import './waves.css';
const vueWaves = {};
vueWaves.install = (Vue, options = {}) => {
  Vue.directive('waves', {
    bind(el, binding) {
      el.addEventListener('click', e => {
        const customOpts = Object.assign(options, binding.value);
        const opts = Object.assign({
          ele: el, // 波纹作用元素
          type: 'hit', // hit点击位置扩散center中心点扩展
          color: 'rgba(0, 0, 0, 0.15)' // 波纹颜色
        }, customOpts),
          target = opts.ele;
        if (target) {
          target.style.position = 'relative';
          target.style.overflow = 'hidden';
          const rect = target.getBoundingClientRect();
          let ripple = target.querySelector('.waves-ripple');
          if (!ripple) {
            ripple = document.createElement('span');
            ripple.className = 'waves-ripple';
            ripple.style.height = ripple.style.width = Math.max(rect.width, rect.height) + 'px';
            target.appendChild(ripple);
          } else {
            ripple.className = 'waves-ripple';
          }
          switch (opts.type) {
            case 'center':
              ripple.style.top = (rect.height / 2 - ripple.offsetHeight / 2) + 'px';
              ripple.style.left = (rect.width / 2 - ripple.offsetWidth / 2) + 'px';
              break;
            default:
              ripple.style.top = (e.pageY - rect.top - ripple.offsetHeight / 2 - document.body.scrollTop) + 'px';
              ripple.style.left = (e.pageX - rect.left - ripple.offsetWidth / 2 - document.body.scrollLeft) + 'px';
          }
          ripple.style.backgroundColor = opts.color;
          ripple.style.overflow = 'hidden';
          ripple.className = 'waves-ripple z-active';
          return false;
        }
      }, false);
    }
  })
};
export default vueWaves;
.waves-ripple {
  position: absolute;
  border-radius: 100%;
  background-color: rgba(0, 0, 0, 0.15);
  background-clip: padding-box;
  pointer-events: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-transform: scale(0);
  -ms-transform: scale(0);
  transform: scale(0);
  opacity: 1;
}

.waves-ripple.z-active {
  opacity: 0;
  -webkit-transform: scale(2);
  -ms-transform: scale(2);
  transform: scale(2);
  -webkit-transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
  transition: opacity 1.2s ease-out, -webkit-transform 0.6s ease-out;
  transition: opacity 1.2s ease-out, transform 0.6s ease-out;
  transition: opacity 1.2s ease-out, transform 0.6s ease-out, -webkit-transform 0.6s ease-out;
}
-------- 本文结束感谢阅读 --------

这里是评论区,如果你看到这段话,就是没加载出来,刷新一下~


集中精神,以气御剪