ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Carbon Design System overflowHandler 溢出处理工具:从配置到源码的完整实战指南

Carbon Design System overflowHandler 溢出处理工具:从配置到源码的完整实战指南 Carbon Design System overflowHandler 溢出处理工具从配置到源码的完整实战指南【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbonoverflowHandler是 IBM Carbon Design System当前仓库carbo/carbon中carbon/utilities包提供的一个框架无关的列表溢出截断工具当容器放不下全部子元素时自动计算哪些项应可见、哪些应隐藏并通过data-hidden属性驱动展示。本文以 overflowHandler README 为主体结合源码、测试与真实组件Card、PageHeader用法帮助你掌握其全部配置项、尺寸计算原理、生命周期管理以及如何复刻“溢出菜单 / N more”这类典型交互。overflowHandler 是什么在真实产品界面中工具栏、卡片操作区、页面头部操作区常常需要在一行内放下数量不定的按钮或标签。空间不足时我们不能简单让元素溢出换行而是需要把“放不下”的项收进溢出菜单Overflow Menu或 popover 中——这就是列表溢出截断overflow truncation要解决的问题。overflowHandler就是为此设计的通用工具它有四个关键特性框架无关不依赖 React、Vue 或 Lit纯 DOM API 实现任何前端技术栈都能使用自动监听实例化后自动监听容器尺寸变化无需手动在resize事件里处理属性驱动只通过data-hidden、data-fixed、data-offset三个 HTML 属性与 DOM 协作样式完全由实现方掌控回调扩展通过onChange拿到可见/隐藏项的 DOM 节点数组可在任意位置渲染隐藏项。从源码结构看该工具位于 packages/utilities/src/overflowHandler/由三个导出组成createOverflowHandler(options)—— 初始化并返回{ disconnect }句柄updateOverflowHandler(options)—— 单次溢出判定与属性更新的核心算法getSize(el, dimension, gap?)—— 测量单个元素有效尺寸的辅助函数。三者通过 packages/utilities/src/overflowHandler/index.ts 统一导出并最终在 packages/utilities/src/index.ts 汇总为carbon/utilities公共 API因此既可按import { createOverflowHandler } from carbon/utilities引入也可按子路径carbon/utilities/overflowHandler引入。快速开始Lit 示例原文档给出了一个使用 Lit 的完整示例见 README.md要点如下// App.ts import { html } from lit; import { createOverflowHandler } from carbon/utilities; let handler; const initializeHandler () { if (handler) { console.log(Handler found. Removing and re-initiating...); document.removeEventListener(DOMContentLoaded, initializeHandler); handler.disconnect(); return; } // initiate the handler only when the DOM settles and is stable, so that the items are at the correct dimensions before initialization. requestAnimationFrame(() { handler createOverflowHandler({ container: document.querySelector(#visible-items), maxVisibleItems: 5, onChange: (visibleItems, hiddenItems) { console.log(visibleItems, hiddenItems); }, dimension: width, }); }); }; document.addEventListener(DOMContentLoaded, initializeHandler); return html style /* Scope if necessary */ [data-hidden]:not([data-fixed]) { display: none; } /style div idvisible-items buttonbutton 1/button buttonbutton 2/button buttonbutton 3/button buttonbutton 4/button buttonbutton 5/button buttonbutton 6/button /div ;运行流程拆解延迟到 DOM 稳定后初始化在DOMContentLoaded之后再用requestAnimationFrame启动确保容器与子项已完成布局、尺寸正确——README 明确注释了这一点实例化 handler传入容器、可见上限、回调与测量维度自动监听与标记handler 监听容器渲染/尺寸变化为“应隐藏”的项加上data-hidden属性由实现方 CSS如示例中的[data-hidden]:not([data-fixed]) { display: none; }负责真正隐藏回调触发当容器宽度变化导致可见项数量变化时onChange(visibleItems, hiddenItems)被调用两个数组分别给出可见与隐藏的节点。在上面例子中#visible-items容器按width维度被监控容器变小到放不下全部按钮时放不下的按钮会被打上data-hidden可见项数量一旦变化就触发onChange。由于设置了maxVisibleItems: 5即使空间足够最多也只显示 5 个按钮。注意示例中return html前的函数体属于示意性伪代码真实 Lit 组件中应在组件的生命周期回调如firstUpdated里执行初始化逻辑并记得在disconnectedCallback中调用handler.disconnect()。三个核心属性属性说明data-hiddenhandler 主动添加在“应隐藏”的项上可见项会被移除该属性样式层据此隐藏元素data-fixed带有该属性的项完全退出溢出管理不计入可见/隐藏判定始终展示data-offset添加到“偏移元素”上——该元素承载所有溢出项如 popover、溢出菜单、tooltip、modal 等handler 会按溢出情况控制它的显隐从 overflowHandler.ts 的源码可见三者的分工方式初始化时容器children会被分为三组——offset带data-offset的元素、fixedItems带data-fixed的元素、以及常规items其余子元素即被管理的项。其中data-offset的显隐规则在 updateOverflowHandler 中实现当没有任何项被隐藏时offset元素被打上data-hidden隐藏“查看更多”入口一旦出现隐藏项则移除该属性让溢出菜单/“N more”按钮出现。测试 overflowHandler-test.js 覆盖了这四种组合场景全部可见时隐藏 offset、有隐藏项时显示 offset 等。Options 完整参数说明原文档的选项表完整继承如下OptionTypeDefaultDescriptioncontainerHTMLElement—被管理子元素溢出行为的容器元素onChange(visibleItems, hiddenItems) void—每当可见或隐藏项集合发生变化时被调用dimensionwidth \| heightwidth沿哪个轴测量溢出maxVisibleItemsnumber—无论空间是否充足可见项数量的硬上限gapnumber0容器column-gapwidth 时或row-gapheight 时的像素值。当容器使用 flex/grid gap 时传入使每个项的“成本”包含其后的间隙offsetValuenumber0从容器的可用空间中预留的像素数使溢出提前触发。适合容器内部还有需要保证空间的元素如“show more”按钮时使用初始化参数校验createOverflowHandler在 overflowHandler.ts 中对入参做了严格校验不满足条件会直接抛错container必须是HTMLElement否则抛container must be an HTMLElementonChange必须是函数否则抛onChange must be a functionmaxVisibleItems若传入必须是正整数Number.isInteger且 0传0、负数或小数如1.5都会抛maxVisibleItems must be a positive integer。对应测试见 overflowHandler-test.js覆盖了0、浮点、负数三种非法输入。底层工作原理源码级解析初始化与监听createOverflowHandler的核心逻辑overflowHandler.ts校验入参后快照容器子元素Array.from(container.children)按data-offset/data-fixed分组定义update()调用updateOverflowHandler完成一次完整判定并保存previousHiddenItems用于变化检测定义scheduleUpdate()通过requestAnimationFrame节流更新——同一帧内多次触发只排队一次rafId ! undefined时直接返回创建ResizeObserver观察容器尺寸变化尺寸一变即调度更新额外监听document.fonts?.ready——字体加载完成后再次调度更新避免 Web 字体换入后项宽变化导致的误判返回{ disconnect() }置disconnected标志、断开ResizeObserver、取消未执行的 rAF。测试 overflowHandler-test.js 验证了三个关键行为ResizeObserver 触发且隐藏集合变化时调用onChange同帧内多次触发 ResizeObserver 只执行一次更新rAF 去重disconnect()会取消挂起的 rAF。溢出判定算法updateOverflowHandleroverflowHandler.ts的判定分为两条分支分支一全部放得下。当满足totalSize totalFixedSize - trailingGap containerSize - offsetValue时所有项都可见若设置了maxVisibleItems则只保留前 N 个其余隐藏。其中trailingGap是对 CSS gap 语义的修正因为每个项测得的尺寸里都包含了一个“项后间隙”而 CSS gap 只在项与项之间存在、最后一个项后面没有 gap所以要把多算的这一个 gap 减回去。分支二放不下逐项填充。可用空间为available containerSize - offsetSize - totalFixedSize - offsetValue gapoffsetSize是data-offset元素自身尺寸totalFixedSize是固定项总尺寸。然后从头遍历常规项accumulated size available且未超过maxVisibleItems的项依次放入可见列表第一个放不下的项作为breakIndex其后全部隐藏。判定完成后统一执行属性更新可见项removeAttribute(data-hidden)隐藏项setAttribute(data-hidden, )并根据隐藏项是否为空切换offset的data-hidden。变化检测如果新一轮的隐藏项数组与previousHiddenItems完全相同长度相等且逐项相等则直接返回、不调用onChange——避免无意义的重复回调测试见 overflowHandler-test.js。项尺寸Item sizing如何计算每个项的有效尺寸按如下公式计算effectiveSize boundingClientRect[dimension] margin (inline-start inline-end) ← width 维度 margin (block-start block-end) ← height 维度 gap即gap、以及项上的 CSS 外边距margin都已计入如果这些值发生动态变化需要重新初始化 handler。这一公式在getSize函数overflowHandler.ts中逐字实现且有三处值得注意的细节隐藏项也能被测量若元素当前不可见offsetParent为空且计算样式为display: none会临时把display设为inline-block完成测量随后立即还原——这样已被data-hidden的项在下一轮重算时依然有真实尺寸不重复计算 paddinggetBoundingClientRect()返回的是 border-box已含 padding因此getSize只加 margin 和 gap绝不追加 padding。测试 overflowHandler-test.js 专门验证了“padding 不重复计数”轴向正交测量width时只累加marginLeft/marginRight测量height时只累加marginTop/marginBottom测试见 overflowHandler-test.js。使用 gap 选项将容器的column-gapwidth 维度或row-gapheight 维度作为gap传入handler createOverflowHandler({ container: document.querySelector(#toolbar), gap: 8, // matches gap: 8px on the flex container onChange: (visibleItems, hiddenItems) { ... }, });由于 CSS gap 只存在于项与项之间handler不会为最后一个可见项之后计数 gap详见上文trailingGap修正。测试给出了两个典型结果overflowHandler-test.js5 个 40px 项、gap 10px、容器 200px5 × (40 10) 250 210200 末项 gap 回补 10因此只显示 4 个隐藏 1 个3 个 40px 项、gap 8px、容器 200px3 × 40 2 × 8 136 ≤ 200全部可见不触发onChange。使用 offsetValue 选项offsetValue用于在项开始填充之前就从容器的可用空间中预留像素——该值被从可用空间中减去因此溢出会提前这么多像素触发。典型场景容器内部有一个“N more”按钮或溢出指示器即使容器接近满载也必须为其保留空间。handler createOverflowHandler({ container: document.querySelector(#toolbar-items), offsetValue: 48, // reserve 48px for a N more button inside the container onChange: (visibleItems, hiddenItems) { ... }, });测试验证了三个组合行为overflowHandler-test.js容器 190px、5 个 40px 项、offsetValue: 40可用空间 190 − 40 150只能放 3 个3 × 40 120第 4 个 160 150隐藏 2 个gap与offsetValue可叠加生效即使所有项本可以放下3 × 40 120 200offsetValue: 100也会把有效空间压到 100强制溢出显示 2 个、隐藏 1 个。通过 onChange 自定义处理onChange(visibleItems, hiddenItems)的回调签名提供了可见与隐藏元素的数组。这给了实现方完全的自由度你可以取出隐藏项把它们渲染到任何地方——modal、popover、溢出菜单、tooltip 都行。仓库内 PageHeader 组件就是这么做的PageHeader.tsxcreateOverflowHandler({ container: containerRef.current, // exclude the hidden menu button from children maxVisibleItems: containerRef.current.children.length - 1, onChange: (visible, hidden) { setHiddenItems(actions?.slice(visible.length)); if (hidden.length 0) { setMenuButtonVisibility(true); } }, });组件维护一个带data-offset的span包裹的MenuButtonPageHeader.tsx当onChange报告有隐藏项时显示溢出菜单按钮并把隐藏项以MenuItem形式渲染进菜单隐藏项被setHiddenItems存进 state 后即可在菜单中反向排列展示。这是一个把“容器内放不下的操作”迁移到“溢出菜单”的标准实现范式。重新初始化指南增删项时必须重新初始化因为createOverflowHandler在创建时对container.children做了快照overflowHandler.ts之后新增/移除的子元素不会被自动纳入管理尺寸相关样式变化每次更新容器 resize都会重新测量所有项因此 padding、margin、gap、offset 尺寸变化后仅靠容器 resize 即可自动校正无 resize 的样式变化如果 padding/margin/gap/offset 尺寸变化但容器尺寸没变比如某项被 JS 改宽了需要调用disconnect()并重新createOverflowHandler。仓库内 Card 组件展示了标准的重建模式card-actions.ts在slotchange后等待updateComplete随后先this._overflowHandler?.disconnect()再重新createOverflowHandler——增删操作项slot 内容变化即触发重建完全符合上述指南。仓库内的完整生产案例Web Components 版 Card 操作区card-actions.ts 是carbon/utilities/overflowHandler在 Web ComponentsLit中的生产级用法初始化时传入gap: 8卡片操作区是 8px 间距的 flex 容器用_ensureOffsetEl()动态创建并追加一个带data-offset的占位 divcard-actions.ts尺寸取自 CSS 变量--cds-spacing-07默认 2remonChange里根据隐藏项数量控制 offset 元素的display并把隐藏项 id 存入_hiddenIdsstate模板据此渲染cds-overflow-menu溢出菜单card-actions.ts组件卸载时调用_overflowHandler?.disconnect()并移除 offset 元素card-actions.ts。Storybook 交互演示若想在浏览器里手动拖拽验证width/height两个维度的溢出表现可参考两个 StoryWeb Components 版overflow-handler.stories.ts右下角拖拽手柄改变容器宽高实时输出 visible/hidden 标签列表并支持maxVisibleItems、gap、offsetValue、fixedIndex、dimension等参数的组合调试React 版OverflowHandler.stories.js与前者镜像对应。测试覆盖与质量保障该工具在 overflowHandler-test.js 中有完整测试可作为行为契约参考getSize空值返回 0宽/高测量margin 与 gap 叠加padding 不重复计数隐藏元素临时显示后再还原L55-L198createOverflowHandler 参数校验container、onChange、maxVisibleItems 三类非法输入L213-L258等宽/不等宽项在不同容器宽度下的可见数如表驱动用例500px→10/0、200px→5/5、80px→2/8等L260-L345gap / offsetValue 的各种组合L347-L440height 维度对称行为L443-L506ResizeObserver 联动、rAF 去重、disconnect 取消挂起任务L508-L566。总结overflowHandler把“列表溢出截断”这一高频交互抽象成了一套小而完整的方案初始化时快照容器子元素并按data-fixed/data-offset分组更新时按dimension逐项填充可用空间并同步data-hidden属性变化时通过onChange通知实现方。核心算法updateOverflowHandler对 CSS gap 的尾项修正、对隐藏元素的临时测量、对字体加载与 ResizeObserver 的双重监听都是实践中容易踩坑的细节——这些在源码与测试中均有明确实现与验证。接入时只需记住三件事在 DOM 稳定后初始化、增删项后重建 handler、data-hidden的隐藏样式由自己声明。剩下的尺寸计算、resize 监听与属性维护全部交给createOverflowHandler即可。【免费下载链接】carbonA design system built by IBM项目地址: https://gitcode.com/GitHub_Trending/carbo/carbon创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表