Vue3骨架屏(Skeleton)

news2024/9/20 18:36:59

效果如下图:在线预览

在这里插入图片描述
在这里插入图片描述

APIs

参数说明类型默认值必传
animated是否展示动画效果booleantruefalse
button是否使用按钮占位图boolean | SkeletonButtonPropsfalsefalse
avatar是否显示头像占位图boolean | SkeletonAvatarPropsfalsefalse
input是否使用输入框占位图boolean | SkeletonInputPropsfalsefalse
image是否使用图像占位图booleanfalsefalse
title是否显示标题占位图boolean | SkeletonTitlePropstruefalse
paragraph是否显示段落占位图boolean | SkeletonParagraphPropstruefalse
loadingtrue 时,显示占位图,反之则直接展示子组件booleantruefalse

SkeletonButtonProps Type

名称说明类型必传
shape指定按钮的形状‘default’ | ‘round’ | ‘circle’false
size设置按钮的大小‘default’ | ‘small’ | ‘large’false
block将按钮宽度调整为其父宽度的选项booleanfalse

SkeletonAvatarProps Type

名称说明类型必传
shape指定头像的形状‘circle’ | ‘square’false
size设置头像占位图的大小number | ‘default’ | ‘small’ | ‘large’false

SkeletonInputProps Type

名称说明类型必传
size设置输入框的大小‘default’ | ‘small’ | ‘large’false

SkeletonTitleProps Type

名称说明类型必传
width设置标题占位图的宽度number | stringfalse

SkeletonParagraphProps Type

名称说明类型必传
rows设置段落占位图的行数number | stringfalse
width设置段落占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度number | string | Array<number|string>false

创建骨架屏组件Skeleton.vue

<script setup lang="ts">
import { computed } from 'vue'

interface SkeletonButtonProps {
  shape?: 'default'|'round'|'circle' // 指定按钮的形状
  size?: 'default'|'small'|'large' // 设置按钮的大小
  block?: boolean // 将按钮宽度调整为其父宽度的选项
}
interface SkeletonAvatarProps {
  shape?: 'circle'|'square' // 指定头像的形状
  size?: number|'default'|'small'|'large' // 设置头像占位图的大小
}
interface SkeletonInputProps {
  size: 'default'|'small'|'large' // 设置输入框的大小
}
interface SkeletonTitleProps {
  width?: number|string // 设置标题占位图的宽度
}
interface SkeletonParagraphProps {
  rows?: number|string // 设置段落占位图的行数
  width?: number|string|Array<number|string>	// 设置段落占位图的宽度,若为数组时则为对应的每行宽度,反之则是最后一行的宽度
}
interface Props {
  animated?: boolean // 是否展示动画效果
  button?: boolean|SkeletonButtonProps // 是否使用按钮占位图
  avatar?: boolean|SkeletonAvatarProps // 是否显示头像占位图
  input?: boolean|SkeletonInputProps // 是否使用输入框占位图
  image?: boolean // 是否使用图像占位图
  title?: boolean|SkeletonTitleProps // 是否显示标题占位图
  paragraph?: boolean|SkeletonParagraphProps // 是否显示段落占位图
  loading?: boolean // 为 true 时,显示占位图,反之则直接展示子组件
}
const props = withDefaults(defineProps<Props>(), {
  animated: true,
  button: false,
  image: false,
  avatar: false,
  input: false,
  title: true,
  paragraph: true,
  loading: true
})
const buttonSize = computed(() => {
  if (typeof props.button === 'object') {
    if (props.button.size === 'large') {
      return 40
    }
    if (props.button.size === 'small') {
      return 24
    }
    return 32
  }
})
const titleTop = computed(() => {
  if (typeof props.avatar === 'boolean') {
    return 8
  } else {
    if (typeof props.avatar.size === 'number') {
      return (props.avatar.size - 16) / 2
    } else {
      const topMap = {
        default: 8,
        small: 4,
        large: 12
      }
      return topMap[props.avatar.size || 'default']
    }
  }
})
const titleWidth = computed(() => {
  if (typeof props.title === 'boolean') {
    return '38%'
  } else {
    if (typeof props.title.width === 'number') {
      return props.title.width + 'px'
    }
    return props.title.width || '38%'
  }
})
const paragraphRows = computed(() => {
  if (typeof props.paragraph === 'boolean') {
    return 3
  }
  return props.paragraph.rows
})
const paragraphWidth = computed(() => {
  if (typeof props.paragraph === 'boolean') {
    return Array(paragraphRows.value)
  } else {
    if (Array.isArray(props.paragraph.width)) {
      return props.paragraph.width.map((width: number|string) => {
        if (typeof width === 'number') {
          return width + 'px'
        } else {
          return width
        }
      })
    } else if (typeof props.paragraph.width === 'number') {
      return Array(paragraphRows.value).fill(props.paragraph.width + 'px')
    } else {
      return Array(paragraphRows.value).fill(props.paragraph.width)
    }
  }
})
</script>
<template>
  <div
    v-if="loading"
    :class="['m-skeleton', {'m-skeleton-avatar': avatar, 'm-skeleton-animated': animated}]"
    :style="`--button-size: ${buttonSize}px; --title-top: ${titleTop}px;`">
    <span
      v-if="button"
      :class="[
        'u-skeleton-button',
        {
          'u-button-round': typeof button !== 'boolean' && button.shape === 'round',
          'u-button-circle': typeof button !== 'boolean' && button.shape === 'circle',
          'u-button-sm': typeof button !== 'boolean' && button.size === 'small',
          'u-button-lg': typeof button !== 'boolean' && button.size === 'large',
          'u-button-block': typeof button !== 'boolean' && button.shape !== 'circle' && button.block,
        }
      ]"></span>
    <span
      :class="[
        'u-skeleton-input',
        {
          'u-input-sm': typeof input !== 'boolean' && input.size === 'small',
          'u-input-lg': typeof input !== 'boolean' && input.size === 'large',
        }
      ]" v-if="input"></span>
    <div class="m-skeleton-image" v-if="image">
      <svg viewBox="0 0 1098 1024" xmlns="http://www.w3.org/2000/svg" class="m-skeleton-image-svg">
        <path class="u-skeleton-image-path" d="M365.714286 329.142857q0 45.714286-32.036571 77.677714t-77.677714 32.036571-77.677714-32.036571-32.036571-77.677714 32.036571-77.677714 77.677714-32.036571 77.677714 32.036571 32.036571 77.677714zM950.857143 548.571429l0 256-804.571429 0 0-109.714286 182.857143-182.857143 91.428571 91.428571 292.571429-292.571429zM1005.714286 146.285714l-914.285714 0q-7.460571 0-12.873143 5.412571t-5.412571 12.873143l0 694.857143q0 7.460571 5.412571 12.873143t12.873143 5.412571l914.285714 0q7.460571 0 12.873143-5.412571t5.412571-12.873143l0-694.857143q0-7.460571-5.412571-12.873143t-12.873143-5.412571zM1097.142857 164.571429l0 694.857143q0 37.741714-26.843429 64.585143t-64.585143 26.843429l-914.285714 0q-37.741714 0-64.585143-26.843429t-26.843429-64.585143l0-694.857143q0-37.741714 26.843429-64.585143t64.585143-26.843429l914.285714 0q37.741714 0 64.585143 26.843429t26.843429 64.585143z"></path>
      </svg>
    </div>
    <div class="m-skeleton-header" v-if="avatar">
      <span
        :class="[
          'u-skeleton-avatar',
          {
            'u-avatar-sm': typeof avatar !== 'boolean' && avatar.size === 'small',
            'u-avatar-lg': typeof avatar !== 'boolean' && avatar.size === 'large',
            'u-avatar-square': typeof avatar !== 'boolean' && avatar.shape === 'square',
          }
        ]"></span>
    </div>
    <template v-if="!button && !image && !input">
      <div class="m-skeleton-content">
        <h3 class="u-skeleton-title" :style="{ width: titleWidth }"></h3>
        <ul class="u-skeleton-paragraph">
          <li v-for="n in paragraphRows" :key="n" :style="`width: ${paragraphWidth[(n as number) - 1]};`"></li>
        </ul>
      </div>
    </template>
  </div>
  <slot v-else></slot>
</template>
<style lang="less" scoped>
.m-skeleton {
  display: table;
  width: 100%;
  .u-skeleton-button {
    display: inline-block;
    vertical-align: top;
    background: rgba(0, 0, 0, .06);
    border-radius: 4px;
    width: 64px;
    min-width: 64px;
    height: 32px;
    line-height: 32px;
  }
  .u-button-sm {
    width: 48px;
    min-width: 48px;
    height: 24px;
    line-height: 24px;
  }
  .u-button-lg {
    width: 80px;
    min-width: 80px;
    height: 40px;
    line-height: 40px;
  }
  .u-button-round {
    border-radius: var(--button-size);
  }
  .u-button-circle {
    width: var(--button-size);
    min-width: var(--button-size);
    border-radius: 50%;
  }
  .u-button-block {
    width: 100%;
  }
  .u-skeleton-input {
    display: inline-block;
    vertical-align: top;
    background: rgba(0, 0, 0, 0.06);
    border-radius: 4px;
    width: 160px;
    min-width: 160px;
    height: 32px;
    line-height: 32px;
  }
  .u-input-sm {
    width: 120px;
    min-width: 120px;
    height: 24px;
    line-height: 24px;
  }
  .u-input-lg {
    width: 200px;
    min-width: 200px;
    height: 40px;
    line-height: 40px;
  }
  .m-skeleton-image {
    display: flex;
    align-items: center;
    justify-content: center;
    vertical-align: top;
    background: rgba(0, 0, 0, .06);
    border-radius: 4px;
    width: 96px;
    height: 96px;
    line-height: 96px;
    .m-skeleton-image-svg {
      width: 48px;
      height: 48px;
      line-height: 48px;
      max-width: 192px;
      max-height: 192px;
      .u-skeleton-image-path {
        fill: #bfbfbf;
      }
    }
  }
  .m-skeleton-header {
    display: table-cell;
    padding-right: 16px;
    vertical-align: top;
    .u-skeleton-avatar {
      display: inline-block;
      vertical-align: top;
      background: rgba(0, 0, 0, .06);
      width: 32px;
      height: 32px;
      line-height: 32px;
      border-radius: 50%;
    }
    .u-avatar-sm {
      width: 24px;
      height: 24px;
      line-height: 24px;
    }
    .u-avatar-lg {
      width: 40px;
      height: 40px;
      line-height: 40px;
    }
    .u-avatar-square {
      border-radius: 6px;
    }
  }
  .m-skeleton-content {
    display: table-cell;
    width: 100%;
    vertical-align: top;
    .u-skeleton-title {
      margin: 0;
      height: 16px;
      background: rgba(0, 0, 0, .06);
      border-radius: 4px;
    }
    .u-skeleton-paragraph {
      margin-top: 24px;
      padding: 0;
      li {
        height: 16px;
        list-style: none;
        background: rgba(0, 0, 0, .06);
        border-radius: 4px;
        &:not(:first-child) {
          margin-top: 16px;
        }
        &:last-child {
          width: 61%;
        }
      }
    }
  }
}
.m-skeleton-avatar {
  .m-skeleton-content {
    .u-skeleton-title {
      margin-top: var(--title-top);
    }
    .u-skeleton-paragraph {
      margin-top: 28px;
    }
  }
}
.m-skeleton-animated {
  .u-skeleton-button,
  .u-skeleton-input,
  .m-skeleton-image,
  .m-skeleton-header .u-skeleton-avatar,
  .m-skeleton-content .u-skeleton-title,
  .m-skeleton-content .u-skeleton-paragraph li {
    position: relative;
    z-index: 0;
    overflow: hidden;
    background: transparent;
    &::after {
      position: absolute;
      top: 0;
      left: -150%;
      bottom: 0;
      right: -150%;
      background: linear-gradient(90deg, rgba(0, 0, 0, .06) 25%, rgba(0, 0, 0, .15) 37%, rgba(0, 0, 0, .06) 63%);
      animation-name: skeleton-loading;
      animation-duration: 1.4s;
      animation-timing-function: ease;
      animation-iteration-count: infinite;
      content: "";
    }
    @keyframes skeleton-loading {
      0% {
        transform: translateX(-37.5%);
      }
      100% {
        transform: translateX(37.5%);
      }
    }
  }
}
</style>

在要使用的页面引入

<script setup lang="ts">
import Skeleton from './Skeleton.vue'
import { ref } from 'vue'

const loading = ref<boolean>(false)

const showSkeleton = () => {
  loading.value = true
  setTimeout(() => {
    loading.value = false
  }, 2000)
}
const animated = ref(false)
const block = ref(false)
const size = ref('default')
const buttonShape = ref('default')
const avatarShape = ref('circle')
const sizeOptions = ref([
  {
    label: 'Default',
    value: 'default'
  },
  {
    label: 'Large',
    value: 'large'
  },
  {
    label: 'Small',
    value: 'small'
  }
])
const buttonShapeOptions = ref([
  {
    label: 'Default',
    value: 'default'
  },
  {
    label: 'Round',
    value: 'round'
  },
  {
    label: 'Circle',
    value: 'circle'
  }
])
const avatarShapeOptions = ref([
  {
    label: 'Square',
    value: 'square'
  },
  {
    label: 'Circle',
    value: 'circle'
  }
])
</script>
<template>
  <div>
    <h1>{{ $route.name }} {{ $route.meta.title }}</h1>
    <h2 class="mt30 mb10">基本使用</h2>
    <Skeleton />
    <h2 class="mt30 mb10">复杂的组合</h2>
    <Skeleton avatar :paragraph="{ rows: 4 }" />
    <h2 class="mt30 mb10">包含子组件</h2>
    <Button :loading="loading" @click="showSkeleton">Show Skeleton</Button>
    <br/>
    <br/>
    <Skeleton :loading="loading">
      <div>
        <h4>Vue Amazing UI, a design language</h4>
        <br/>
        <p>
          We supply a series of design principles, practical patterns and high quality design
          resources, to help people create their product prototypes beautifully and efficiently.
        </p>
      </div>
    </Skeleton>
    <h2 class="mt30 mb10">自定义标题和段落</h2>
    <Skeleton avatar :title="{ width: '24%' }" :paragraph="{ rows: 4, width: ['48%', '72%', '96%', '60%'] }" />
    <h2 class="mt30 mb10">按钮 / 输入框 / 图像 / 头像</h2>
    <Flex :gap="32">
      <Flex vertical :gap="12" width="50%">
        <Skeleton :animated="animated" :button="{ shape: buttonShape, size: size, block: block}" />
        <Skeleton style="width: 200px" :animated="animated" :input="{ size: size }" />
        <Skeleton :animated="animated" image />
        <Skeleton :avatar="{ shape: avatarShape, size: size }" :paragraph="{ rows: 2 }" />
      </Flex>
      <Flex vertical :gap="36" width="50%">
        <Space :size="32">
          <Space align="center">
            animated: <Switch v-model:checked="animated" />
          </Space>
          <Space align="center">
            Button Block: <Switch v-model:checked="block" />
          </Space>
        </Space>
        <Space align="center">
          Size: <Radio :options="sizeOptions" v-model:value="size" />
        </Space>
        <Space align="center">
          Button Shape: <Radio :options="buttonShapeOptions" v-model:value="buttonShape" />
        </Space>
        <Space align="center">
          Avatar Shape: <Radio :options="avatarShapeOptions" v-model:value="avatarShape" />
        </Space>
      </Flex>
    </Flex>
  </div>
</template>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1704142.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

FPGA之tcp/udp

在调试以太网的过程中&#xff0c;考虑了vivado IP配置(管脚、reset等)&#xff0c;SDK中PHY芯片的配置(芯片地址、自适应速率配置等)&#xff0c;但是&#xff0c;唯独忽略了tcp/udp协议&#xff0c;所以在ping通之后仍无法连接。 所以现在来学习一下tcp与udp的区别 ---- 为什…

FTP协议——LightFTP安装(Linux)

1、简介 LightFTP是一个轻量级的FTP&#xff08;File Transfer Protocol&#xff0c;文件传输协议&#xff09;客户端软件。FTP是一种用于在网络上传输文件的标准协议&#xff0c;允许用户通过TCP/IP网络&#xff08;如互联网&#xff09;在计算机之间进行文件传输。 2、步骤…

sqlserver——查询(四)——连接查询

目录 一.连接查询 分类&#xff1a; 内连接&#xff1a; 1. select ... from A&#xff0c;B &#xff1b; 2. select ..from A&#xff0c;B where ..&#xff1b; 3.select ...,... from A join B on... 4. where 与 join...on 的区别 5. where位置的先后 导语&#xff1…

统计计算四|蒙特卡罗方法(Monte Carlo Method)

系列文章目录 统计计算一|非线性方程的求解 统计计算二|EM算法&#xff08;Expectation-Maximization Algorithm&#xff0c;期望最大化算法&#xff09; 统计计算三|Cases for EM 文章目录 系列文章目录一、基本概念&#xff08;一&#xff09;估算 π \pi π&#xff08;二&…

antd(react) ProFormUploadDragger(Upload)上传组件上传图片及省略图模糊展示

antd&#xff08;react&#xff09; ProFormUploadDragger&#xff08;Upload&#xff09;上传组件上传图片及省略图模糊展示 本文基于reactantdProComponents 需求&#xff1a;图片模糊展示&#xff08;数据脱敏&#xff09; 像营业执照、身份证这种&#xff0c;可能用户不希…

信息学奥赛初赛天天练-13-数论-素数的判定

更多资源请关注纽扣编程微信公众号 1 素数 质数又称素数&#xff0c;有无限个。一个大于1的自然数&#xff0c;除了1和它本身外&#xff0c;不能被其他自然数整除&#xff0c;就是该数除了1和它本身以外不再有其他的因数;否则称为合数 1 既非素数也非合数 2 是唯一的偶素数。…

全局查询筛选器适用场景 以及各场景示例

EF Core中的全局查询筛选器&#xff08;Global Query Filters&#xff09;是一种强大的功能&#xff0c;可以在实体框架的DbContext级别为特定的EntityType设置默认的过滤条件。这些筛选器自动应用于所有涉及到相关实体的LINQ查询中&#xff0c;无论是直接查询还是通过Include或…

深入解读 ChatGPT 的基本原理(个人总结版)

引言 背景 人工智能&#xff08;AI&#xff09;技术自20世纪中期诞生以来&#xff0c;经历了多次革新和进步。从最早的图灵测试&#xff0c;到20世纪末的深蓝计算机击败国际象棋冠军&#xff0c;再到21世纪初谷歌AlphaGo击败围棋冠军&#xff0c;AI技术的飞速发展改变了人们的…

4,八种GPIO模式

资料来源:【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 【STM32基础学习】八种GPIO模式总结-云社区-华为云 (huaweicloud.com) 仅作个人自学笔记&#xff0c;如有冒犯&#xf…

STM32Cube系列教程10:STM32CubeIDE工程创建+串口DMA+IDLE+printf重定向+软中断处理串口数据+非阻塞延时任务

文章目录 工程配置配置时钟配置Debug接口配置串口外设配置时钟树生成代码 配置串口重定向printf配置串口&#xff0c;开启IDLE&#xff0c;开启软中断 配置非阻塞延时任务调度函数编写任务调度函数延时任务创建 编译&#xff0c;下载与测试编译下载测试 前两天收到了ST社区的NU…

C数据结构:二叉树

目录 二叉树的数据结构 前序遍历 中序遍历 后序遍历 二叉树的创建 二叉树的销毁 二叉树的节点个数 二叉树叶子节点个数 二叉树第K层节点个数 二叉树的查找 层序遍历 判断二叉树是否为完全二叉树 完整代码 二叉树的数据结构 typedef char BTDataType; typedef str…

Golang的内存关系

1.Page Golang的Page,在操作系统对虚拟内存管理的MMU定义的物理页有相似的定义,默认的Page为8KB 2.mSpan 多个连续的Page称之为是一个Span&#xff0c;其定义含义有操作系统的管理的页表相似 3.Size Class Size Class: 相当于 一个等级和刻度, 比如 第二等级 就代表 一个Pag…

【C++ ——— 多态】笔记

文章目录 一、多态概念二、多态的定义即实现2.1 多态的构成条件2.2 虚函数2.3虚函数的重写1.虚函数中析构函数的重写2.重载、重写&#xff08;覆盖&#xff09;、重定义&#xff08;隐藏&#xff09;的区别 2.4 C11 override 和 final 三、抽象类3.1抽象类概念3.2 接口继承和实…

中断处理过程介绍

概念 中断 中断源 分类 中断处理过程 中断请求 实现器件 中断判优 软件判优 过程 器件实现 程序实现 硬件判优 链路判优 器件实现 控制器判优 中断响应 中断服务 中断返回

C语言作为计算机行业的基础之一,是否制约了行业本身的发展?

c不是计算机行业的基础啦&#xff0c;你想&#xff0c;c语言出现时已经有一套成熟的计算机体系&#xff0c;有基于内存地址的寻找指令、数据的工作方式&#xff0c;有汇编语言&#xff0c;那搞出c这种高级语言就很正常啊&#xff01;刚好我有一些资料&#xff0c;是我根据网友给…

代码随想录算法训练营第20天 |● 654.最大二叉树 ● 617.合并二叉树 ● 700.二叉搜索树中的搜索 ● 98.验证二叉搜索树

文章目录 前言654.最大二叉树思路方法一 递归法方法一2 老师的优化递归法 617.合并二叉树思路方法一 递归法方法二 迭代法 700.二叉搜索树中的搜索思路方法一 递归法方法二 迭代法 98.验证二叉搜索树思路方法一 使用数组方法二 不使用数组代码注意点&#xff1a; 方法二 使用双…

【Linux】Linux的基本指令_3

文章目录 二、基本指令15. date16. cal16. find17. grep18. zip 和 unzip19. tar20. uname 未完待续 二、基本指令 15. date date 命令可以显示当前时间。 常用标记列表&#xff1a; %H : 小时(00…23) %M : 分钟(00…59) %S : 秒(00…61) %X : 相当于 %H:%M:%S %d : 日 (01……

简易计算器

前言 简易计算器&#xff0c;旨在实现一个简单的计算器功能。 整形&#xff0c;浮点型数据的加减乘除运算&#xff1b;数据的统计(如文件中某字符的出现频数)&#xff1b;期望&#xff0c;方程运算&#xff1b;平均数&#xff0c;最小值&#xff0c;最大值&#xff0c;中位数…

C++之“流”-第2课-C++和C标准输入输出同步

为什么C和C的标准输入输出不同步时&#xff0c;数据会混乱&#xff1f;同步会带来多大性能损失&#xff1f;为什么说这个损失通常不用太在乎&#xff1f; 0. 课堂视频 C之“流”-第2课&#xff1a;和C输入输出的同步 1. 理解cin和cout的类型与创建过程 std::cout 是std::ostre…