123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172 |
- import React, { useEffect, useReducer, useRef } from 'react'
- // import { useModel } from 'umi'
- interface State {
- childrenDivs: Element[],//实例
- children?: JSX.Element[],//jsxElement
- configs?: { w: number, h: number, t: number, l: number }[],//配置
- size?: number,//屏幕宽度
- newColumn?: number//本次展示列数
- }
- interface Action {
- type: 'addRef' | 'children' | 'configs' | 'size' | 'newColumn',
- params?: any,
- }
- function reducer(state: State, action: Action) {
- let { type, params } = action
- switch (type) {
- case 'addRef':
- let arr = state.childrenDivs
- arr[params.index] = params.ref//替换指定位置的实例
- arr = arr?.filter((item: Element) => { return item })//清空空实例
- return { ...state, childrenDivs: arr }
- case 'children':
- return { ...state, children: params.children }
- case 'configs':
- if (JSON.stringify(params.configs) === JSON.stringify(state.configs)) {//传入的配置和老配置一样不更新
- return { ...state }
- }
- return { ...state, configs: params.configs }
- case 'newColumn':
- return { ...state, newColumn: params.newColumn }
- case 'size':
- return { ...state, size: params.size }
- default:
- return { ...state }
- }
- }
- /**
- *
- **高阶函数处理瀑布流传入JSX.ELEMENT
- * @example
- *<HocWaterFallBox>
- * <div>//必要的div包裹,缺少会报错
- * {
- * getData?.data?.items?.map((item: any, index: number) => {
- * return <div key={item.id} className={action.id === item.id ?{...}:{...}}>//必要的div包裹,缺少会报错,不要在此div设置统一className,此处className只在配置选中需求时JS控制添加,当盒子存在className不会做样式更新处理更新注意!
- * <Img src='...' /> //要渲染的盒子模型
- * </div>
- * })
- * }
- * </div>
- *</HocWaterFallBox>
- * @param {number} column 一行展示多少列,column和childrenWidth二选一,设置了column自动计算子元素的宽度
- * @param {number} childrenWidth 默认子元素宽度300,可自行设置支持number,设置了子元素宽列就以宽度计算
- * @param {number} width 容器宽默认100%,可自行设置支持number
- * @param {number} margin 子元素左右间隔默认20px,可自行设置支持number,底部间隔默认为0
- * @param { JSX.Element} props JSX.Element
- * @return { JSX.Element} JSX.Element
- */
- function HocWaterFallBox(props: { children: JSX.Element, childrenWidth?: number, width?: number, margin?: number, column?: number }) {
- const { childrenWidth = 300, width, margin = 20, column } = props
- const [state, dispatch] = useReducer(reducer, { childrenDivs: [], children: null, configs: [] })
- // let { collapsed } = useModel('@@initialState', model => ({ collapsed: model?.initialState?.collapsed }))
- let div: { current: HTMLDivElement | null } = useRef(null)
- let { childrenDivs, configs, children, size, newColumn } = state
- //监听浏览器宽度变化
- useEffect(() => {
- window.onresize = function () {
- dispatch({ type: 'size', params: { size: document.body.scrollWidth } })
- }
- return () => {
- window.onresize = null
- }
- }, [])
- //计算配置
- useEffect(() => {
- let children = props?.children?.props?.children
- let bodyWidth: number = 0//父容器的宽
- let newColumn: number = column ? column : 0 //列
- let allW: number = 0//统一宽
- let childConfg: { w: number, h: number, t: number, l: number }[] = []//每个children的样式配置
- let allH: number[] = []//全部列的高
- let time: any = null//定时器
- function set() {
- if (div?.current) {//计算容器宽和列
- bodyWidth = width ? width : div?.current?.clientWidth//容器宽有自定义就使用自定义没有就计算当前容器的宽
- if (newColumn) {//假如自定义了列数执行
- allW = (bodyWidth - (newColumn - 1) * margin) / newColumn//计算所有children的宽
- } else {//否则走自定义children宽逻辑
- if (bodyWidth <= childrenWidth) {//假如容器宽小于等于渲染元素的宽退出操作避免报错
- return
- }
- newColumn = Math.floor((bodyWidth - childrenWidth) / (childrenWidth + margin)) + 1//计算列
- }
- }
- childrenDivs.forEach((child: Element, index: number) => {//循环拿到的实例
- let w = allW ? allW : child?.clientWidth || 0//children的宽,自定义列就走计算的宽,没有就计算实例的宽,初始为0
- let h = child?.scrollHeight || 0//children的高,从实例计算,初始为0
- let t = 0//children的top值初始为0
- let l = 0//children的left值初始为0
- if (index < newColumn) {//假如下标小于列的值为第一行逻辑
- allH.push(h)//添加第一行所有children的高
- l = index === 0 ? 0 : (allW ? allW : childrenWidth) * index + margin * index//计算第一行每个children的left值
- } else {//否则不是第一行走以下逻辑
- let minH: number = Math.min(...allH)//取当前最小高度的children的值
- let eq = allH.indexOf(minH)//取当前最小高度children的位置
- allH[eq] = allH[eq] + h + margin//更新列的高
- t = minH ? minH + margin : 0//设置当前children的top
- l = eq === 0 ? 0 : (allW ? allW : childrenWidth) * eq + margin * eq//设置当前children的left值
- }
- childConfg.push({ w, h, t, l })//向配置中添加本次计算
- })
- dispatch({ type: 'configs', params: { configs: childConfg } })//存放本次计算的所有children配置
- dispatch({ type: 'newColumn', params: { newColumn: newColumn } })//存放本次计算的列值
- }
- if (childrenDivs?.length === children?.length) {//假如获取到实例的个数和本次传入的jsxElement个数相等表示已经全部取得实例可以进行计算
- time = setTimeout(() => { //设置定时器,避免渲染过慢导致获取相应的数据不正确
- set()
- }, 200)
- }
- return () => {//每次卸载清除定时器以免内存泄漏
- time = null
- clearTimeout(time)
- }
- }, [childrenDivs, div, childrenWidth, width, column, margin, size])//此处依赖值变化需要更新计算
- /**处理结果 */
- useEffect(() => {
- let childrens: JSX.Element[] = props?.children?.props?.children || []
- let allH: number[] = []//全部列的高
- let newChild: { [name: string]: string }[] = []
- let oldChild: { [name: string]: string }[] = []
- childrens?.map((child: any) => {
- newChild.push({ key: child?.key, style: child?.props?.style })
- })
- state?.children?.props?.children?.map((child: any) => {
- oldChild.push({ key: child?.key.replace(/(\/.*)|(")/ig, ''), style: child?.props?.style })
- })
- if (JSON.stringify(newChild) === JSON.stringify(oldChild)) {
- return
- }
- if (!props?.children?.props?.children) {//当没拿到全部数据不处理
- return
- }
- let configss = configs
- if (configss.every((item: { w: number, h: number, t: number, l: number }) => item.l === 0) && configss.length > 0) {//配置没有处理完不处理
- return
- }
- configss?.forEach((conf: { h: number }, index: number) => {//计算全部列的高
- allH[index % newColumn] = allH.length === newColumn ? allH[index % newColumn] + conf.h + margin : conf.h + margin
- })
- let children = React.cloneElement(//创建新的JSX.ELEMENT
- props.children,
- {
- children: React.Children.map(childrens, (child: JSX.Element, index: number) => {
- return React.cloneElement(
- child,
- {
- style: { width: configss[index]?.w || childrenWidth, position: 'absolute', left: configss[index]?.l, top: configss[index]?.t, },
- ref: (ref: HTMLDivElement) => { dispatch({ type: 'addRef', params: { index, ref } }) },
- key: JSON.stringify(child.key)
- },
- )
- }),
- ref: div,
- style: { width: width || '100%', position: 'relative', height: allH.length > 0 ? Math.max(...allH) : '100%' },
- },
- )
- dispatch({ type: 'children', params: { children } })
- }, [props.children, configs, newColumn])
- return children
- }
- export default HocWaterFallBox
|