React 简单模拟一个手机文件夹动效组件

1,593 阅读2分钟
mobile-folder

手机文件夹动效组件

开发技术:react, ts

将一个列表类似手机文件夹那样包裹起来,点击展开/收起。

demo试玩及文档

组件源码

实现思路

首先组件分为4个部分,如图中红色框起来的4个,而第4个里面是用于存放前三个之外的剩余的 item 的;

最终只会看到 3 + 4 = 7item, 而实际会把所有的 item 都渲染出来;第4个部分采用 overflow: hidden,隐藏起其他 item (绿色框起来的部分)

image.png

展开

当点击第四项时,需要弹出并展开所有的 item

Kapture 2023-11-23 at 16.17.05.gif

首先会创建一个弹出层,弹出层中渲染所有跟 item 同样大小的盒子(即下图中绿色的背景部分)以及完成对应的css布局;

然后采用 getBoundingClientRect 方法获取它们的 lefttop 值。再通过计算给组件中的各 item 采用 css 的 transform:translate(?px,?px) 移动到对应位置即可。

image.png
const onShowMore = () => {
  if(isShowMore) return
  document.body.style.overflow = 'hidden';
  // 展开更多 item
  setIsShowMore(true)
  // 清除 overflow: hidden;
  setIsMoreOverflowHidden(false)
  // 延迟,等弹出层渲染完再获取位置信息
  setTimeout(() => {
    const arr = []
    for(let i = 0; i < list?.length; i++) {
      // 获取距离屏幕的 `left` 和 `top` 值
      const itemInfo = document.querySelector(`.${idRef.current} .${classPrefix}-item-${i}`)?.getBoundingClientRect()
      const popItemInfo = document.querySelector(`.${idRef.current} .${classPrefix}-pop-item-${i}`)?.getBoundingClientRect()
      if(!itemInfo || !popItemInfo) continue;
      arr.push({
        // 相减得出实际该位移的值
        x: popItemInfo.left - itemInfo.left,
        y: popItemInfo.top - itemInfo.top, 
      })
    }
    setMoreItems(arr)
  }, 10);
}

收起

将各 item 的位移值清空,让他们归位即可,其中需要注意,等动画进行到差不多时,才重新打开 overflow: hidden,这样才能看到各 item 收起来的样式。

const onHideMore = () => {
  setMoreItems([])
  setIsShowMore(false)
  // css -> transition: transform 0.4s, opacity .2s;
  // 200ms 后溢出项的 opacity 就为 0 了,300ms 后开启溢出隐藏。
  setTimeout(() => {
    setIsMoreOverflowHidden(true)
    document.body.style.overflow = 'auto'
  }, 300);
}

这是初略的描述,建议查看 完整代码 会看得更清晰;

Demo 使用

import { MobileFolder, MobileFolderItem } from "lhh-ui"
import React from "react"
const JuejinIcon = 'https://lf3-cdn-tos.bytescm.com/obj/static/xitu_juejin_web/6c61ae65d1c41ae8221a670fa32d05aa.svg';

export default () => {

  const list: MobileFolderItem[] = [
    {icon: JuejinIcon, title: 'juejin', onClick: () => {window.open('https://github.com/ApeWhoLovesCode/lhh-ui')}},
    {icon: JuejinIcon, title: 'juejin22'},
    {icon: JuejinIcon, title: 'juejin33'},
    {icon: JuejinIcon, title: 'juejin44', onClick: (item, i) => {console.log(item, i)}},
    {icon: JuejinIcon},
    {icon: JuejinIcon},
    {icon: JuejinIcon},
    {icon: JuejinIcon},
    {icon: JuejinIcon},
    {icon: JuejinIcon},
    {icon: JuejinIcon},
  ]

  return (
    <div style={{width: 300}}>
      <MobileFolder list={list} />
    </div>
  )
}

Props

MobileFolderProps

属性名描述类型默认值
list需要渲染的列表MobileFolderItem[](必选)
className类名string--
stylestyle样式{}--
childrenchildren节点ReactNode--

MobileFolderItem

属性名描述类型默认值
icon渲染的内容为图片string--
title标题string--
children自定义渲染的内容ReactNode--
onClick点击的回调(item: MobileFolderItem, i: number) => void--