【vue自定义指令】骨架屏指令

场景

预加载的过程中,数据还未请求到,dom已经渲染出来了?

展示效果


 

实现

封装指令(代码块1)
  1. app引入(代码块2)
  2. 使用(代码块3)

代码

封装

​
import { reactive, watchEffect, h, render } from 'vue';


  const state = reactive({
    loading: false,
    list: []
  });

  // 动态插入 CSS 样式
  const insertStyles = () => {
    const style = document.createElement('style');
    style.textContent = `
    @keyframes fadeIn {
      from { opacity: 0; }
      to { opacity: 1; }
    }
    
    @keyframes fadeOut {
      from { opacity: 1; }
      to { opacity: 0; }
    }
    
    .frame-container {
      animation: fadeIn 0.5s ease-in-out;
    }
    
    .frame-container.fade-out {
      animation: fadeOut 0.5s ease-in-out;
    }
    
    .frame-item {
      background: #e5e5e5;
      border-radius: 4px;
    }
  `;
    document.head.appendChild(style);
  };


  insertStyles(); // 执行插入样式

  watchEffect(() => {
    const children = state.list.map((el) => {
      const rect = el.getBoundingClientRect();
      return h('div', {
        class: 'frame-item',
        style: {
          position: 'absolute',
          top: rect.top + 'px',
          left: rect.left + 'px',
          width: '0px',  // 初始化宽度为0
          height: rect.height + 'px',
          borderRadius: getComputedStyle(el).borderRadius,
          maxWidth: rect.width + 'px',  // 确保动画结束时的最大宽度
        },
      });
    });

    const container = h('div', { class: 'frame-container' }, children);

    if (state.loading) {
      render(container, document.body);

      // 动态触发动画
      requestAnimationFrame(() => {
        state.list.forEach((el, index) => {
          const rect = el.getBoundingClientRect();
          const frameItem = document.querySelectorAll('.frame-item')[index];
          frameItem.style.width = rect.width + 'px';  // 动态设置最终宽度
          frameItem.style.transition = 'width 1.5s ease-in-out';  // 过渡动画
        });
      });
    } else {
      const existingContainer = document.querySelector('.frame-container');
      if (existingContainer) {
        existingContainer.classList.add('fade-out');
        setTimeout(() => {
          render(null, document.body);
        }, 300);
      }
    }
  });


  const Frame = {
    mounted(el, binding) {
      state.loading = binding.value;
    },
    updated(el, binding) {
      state.loading = binding.value;
    },
    unmounted(el) {
      state.loading = false;
    }
  };

  const FrameItem = {
    mounted(el) {
      state.list.push(el);
    },
    unmounted(el) {
      const i = state.list.indexOf(el);
      if (i !== -1) {
        state.list.splice(i, 1);
      }
    }
  };

  const vFrame = {
    install: (app) => {
      app.directive('frame', Frame);
      app.directive('frame-item', FrameItem);
    }
  };

  export default vFrame;

​

使用

<template>
  <div class="mb100" v-frame="frameLoading" :style="{position:'relative',display:'flex',flexDirection:'column',rowGap:'10px'}">
    <div :style="{width:'600px'}">
      <div v-frame-item>
        91273123812738127831738127381273812
      </div>
    </div>
    <div :style="{width:'200px'}">
      <div v-frame-item>
        012931293
      </div>
    </div>
    <div :style="{width:'300px'}">
      <div v-frame-item>
        012931293
      </div>
    </div>
    <div :style="{width:'400px'}">
      <div v-frame-item>
        012931293
      </div>
    </div>
  </div>
</template>

<script setup>
  const frameLoading = ref(true)
</script>


 


 


 


 


 


 


 


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值