【Nova UI】十九、打造组件库之折叠组件(下):深入实现与潜在陷阱

序言

在上篇文章中,我们对折叠组件的开发做了前期铺垫,包括基本概念、思路和准备工作。现在,我们将深入其具体实现啦 !不过,开发过程中会有一些 “小坑” 。本文会详细阐述如何运用 Vue 特性将折叠组件的设计转化为代码,同时分享可能遇到的潜在问题及解决办法,让我们开启精彩的开发之旅 !

Transition

首先介绍一下vue的内置组件Transition:它可以将进入和离开动画应用到通过默认插槽传递给它的元素或组件上。进入或离开可以由以下的条件之一触发:

  • v-if 所触发的切换
  • v-show 所触发的切换
  • 由特殊元素 <component> 切换的动态组件
  • 改变特殊的 key 属性

它有6个CSS class应用于进入与离开过渡效果:

  • enter-from:进入动画的起始状态。在元素插入之前添加,在元素插入完成后的下一帧移除。
  • enter-active:进入动画的生效状态。应用于整个进入动画阶段。在元素被插入之前添加,在过渡或动画完成之后移除。这个 class 可以被用来定义进入动画的持续时间、延迟与速度曲线类型。
  • enter-to:进入动画的结束状态。在元素插入完成后的下一帧被添加 (也就是 v-enter-from 被移除的同时),在过渡或动画完成之后移除。
  • leave-from:离开动画的起始状态。在离开过渡效果被触发时立即添加,在一帧后被移除。
  • leave-active:离开动画的生效状态。应用于整个离开动画阶段。在离开过渡效果被触发时立即添加,在过渡或动画完成之后移除。这个 class 可以被用来定义离开动画的持续时间、延迟与速度曲线类型。
  • leave-to:离开动画的结束状态。在一个离开动画被触发后的下一帧被添加 (也就是 v-leave-from 被移除的同时),在过渡或动画完成之后移除。

除了CSS class也是对应的8个钩子函数的:

  • before-enter: 对应 enter-from
  • enter: 对应 enter-active
  • after-enter: 对应 enter-to
  • enter-cancelled:当进入过渡在完成之前被取消时调用
  • before-leave: 对应 leave-from
  • leave: 对应 leave-active
  • after-leave: 对应 leave-to
  • leave-cancelled:当进入过渡在结束之前被取消时调用 仅在 v-show 过渡中可用

BeforeEnter

过渡效果的本质是 CSS 属性值从一个数值变化到另一个数值。折叠组件原本可通过改变 DOM 元素的高度来实现,但实际开发中,DOM 元素高度往往需要自适应,难以提前确定准确高度,因此改变高度的方案不太可行。不过,我们可以通过改变元素的最大高度来实现。

const listeners = {
  beforeEnter: (el: RendererElement) => {
   if (!el.dataset) el.dataset = {}

    const style = getComputedStyle(el as Element)
    const { height, maxHeight, paddingTop, paddingBottom } = style

    if (parseInt(height, 10)) el.dataset.height = height
    if (parseInt(maxHeight, 10)) el.dataset.maxHeight = maxHeight
    el.dataset.paddingTop = paddingTop
    el.dataset.paddingBottom = paddingBottom

    el.style.maxHeight = 0
    el.style.paddingTop = 0
    el.style.paddingBottom = 0
  },

beforeEnter函数在元素进入过渡动画前执行预处理操作。它接收一个元素el,将元素设置为折叠状态,并记录其原始样式,为后续动画做好准备📋。

Enter

const getMaxHeight = (el: RendererElement) => {
  let result = null
  const { maxHeight, height } = el.dataset
  if (maxHeight && height) {
    result = parseInt(maxHeight, 10) < parseInt(height, 10) ? maxHeight : height
  } else if (height) {
    result = height
  } else if (el.scrollHeight !== 0) {
    result = el.scrollHeight + parseInt(el.dataset.paddingTop, 10) + parseInt(el.dataset.paddingBottom, 10) + 'px'
    result = parseInt(maxHeight, 10) < parseInt(result, 10) ? maxHeight : result
  }
  return result
}

const listeners = {
    enter: (el: RendererElement) => {
      requestAnimationFrame(() => {
        el.dataset.overflow = el.style.overflow

        el.style.maxHeight = getMaxHeight(el)
        el.style.paddingTop = el.dataset.paddingTop
        el.style.paddingBottom = el.dataset.paddingBottom
        el.style.overflow = 'hidden'
    })
  },
}

首先,getMaxHeight 的函数,它接收一个 RendererElement 类型的元素 el 作为参数。该函数的主要目的是计算并返回元素合适的高度值。首先,它尝试从元素的 dataset 中获取 maxHeightheight 属性。若这两个属性都存在,就比较它们转换为整数后的大小,将较小的值赋给结果变量 result;若只有 height 存在,就直接将其赋值给 result。若元素的 scrollHeight 不为 0,会将 scrollHeightdataset 中的 paddingToppaddingBottom 值相加,再转换为带 px 单位的字符串作为新的高度值,接着再与 maxHeight 比较大小,将较小的值赋给 result。最后返回 result

listeners对象中的enter方法在元素进入过渡动画时执行。借助requestAnimationFrame确保在浏览器下一帧渲染前执行后续操作,防止样式闪烁。该方法先将元素当前的overflow样式值存入dataset,再调用getMaxHeight函数获取元素展开时的最大高度并设置到maxHeight样式属性上,接着从dataset恢复元素的顶部和底部内边距样式,最后将overflow样式设为hidden,确保展开过程中内容不会溢出,实现平滑的展开动画🎈。

AfterEnter、EnterCancelled

const reset = (el: RendererElement) => {
  const { maxHeight, height, overflow, paddingTop, paddingBottom } = el.dataset
  el.style.height = height;
  el.style.maxHeight = maxHeight;
  el.style.overflow = overflow;
  el.style.paddingTop = paddingTop; 
  el.style.paddingBottom = paddingBottom; 
}
const listeners = {
    afterEnter: (el: RendererElement) => {
      el.style.overflow = el.dataset.overflow
    },
    enterCancelled: (el: RendererElement) => {
      reset(el)
    },
}

reset函数负责将元素样式恢复到原始状态。它从元素的dataset中获取之前存储的高度、最大高度、溢出处理方式、顶部和底部内边距等样式信息,并重新应用到元素的style属性上。

afterEnter方法在元素展开动画结束后执行,将元素的overflow样式恢复为dataset中存储的值,保证元素最终的溢出显示状态符合初始设置。

enterCancelled方法在元素展开动画被取消时执行,调用reset函数,将元素样式重置为动画开始前的状态,维持界面的一致性🖥️。

BeforeLeave

const listeners = {
    beforeLeave: (el: RendererElement) => {
      if (!el.dataset) el.dataset = {}
      el.dataset.paddingTop = el.style.paddingTop
      el.dataset.paddingBottom = el.style.paddingBottom
      el.dataset.overflow = el.style.overflow
    
      el.style.maxHeight = getMaxHeight(el)
      el.style.overflow = 'hidden'
    },
}

listeners对象中的beforeLeave方法在元素开始离开过渡动画前进行准备工作。它首先检查元素是否有dataset属性,若没有则进行初始化。接着,将元素当前的顶部内边距、底部内边距和溢出处理方式存储到dataset中,以便后续恢复。然后,调用getMaxHeight函数获取元素合适的最大高度并设置到maxHeight样式属性上,同时将overflow样式设为hidden,避免内容溢出,为离开过渡动画做好样式准备📦。

Leave、AfterLeave、LeaveCancelled

const listeners = {
 beforeLeave: (el: RendererElement) => {
    if (!el.dataset) el.dataset = {}
    el.dataset.paddingTop = el.style.paddingTop
    el.dataset.paddingBottom = el.style.paddingBottom
    el.dataset.overflow = el.style.overflow
    
    el.style.maxHeight = getMaxHeight(el)
    el.style.overflow = 'hidden'
  },
  leave: (el: RendererElement) => {
    if (el.scrollHeight !== 0) {
      el.style.maxHeight = 0
      el.style.paddingTop = 0
      el.style.paddingBottom = 0
    }
  },
  afterLeave: (el: RendererElement) => {
    reset(el)
  },
  leaveCancelled: (el: RendererElement) => {
    reset(el)
  }
}

beforeLeave方法在离开过渡动画开始前,检查元素是否有dataset属性,若没有则初始化。然后将元素当前的顶部和底部内边距、溢出处理方式存储在dataset中,调用getMaxHeight函数获取合适的最大高度并设置给元素,同时将溢出方式设为hidden,为后续离开动画做准备。

leave方法在离开过渡动画开始时执行。若元素有内容(scrollHeight不为 0),将元素的最大高度、顶部和底部内边距都设为 0,使元素开始收缩,实现折叠效果。

afterLeave方法在离开过渡动画结束后执行,调用reset函数将元素样式恢复到初始状态,保证动画结束后元素样式符合预期。

leaveCancelled方法在离开过渡动画被取消时执行,同样调用reset函数,将元素样式重置,避免因动画取消而出现样式异常🧐。

这些钩子函数相互协作,实现了折叠组件离开动画过程中的样式管理和状态恢复,让用户在操作折叠组件时获得流畅的视觉体验。

展示

在这里插入图片描述

🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。
诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨‍💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ !
👉点我

感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 Star ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值