核心解法是用 line-clamp 控制截断,配合 max-height 过渡与 aria-expanded 语义化控制;需确保 display: -webkit-box 与 -webkit-line-clamp 成对使用,合理设置 max-height 并兼顾可访问性、SEO 及 iOS Safari 兼容性。
多行文本截断后加“展开更多”按钮,不是简单隐藏再显示就行——直接
切换会破坏可访问性、影响 SEO、且在 iOS Safari 上常出现布局跳动或高度计算不准。核心解法是用
控制截断,配合
过渡 +
语义化控制。
用
实现真正多行截断(而非 JS 模拟)
现代浏览器(Chrome 68+、Firefox 73+、Safari 15.4+)已原生支持
,它比 JS 计算字符数或测量 DOM 高度更稳定、无闪动、不依赖字体加载时机。
关键点:
和
必须成对出现,仅设
不生效
必须限制容器
或
,否则截断可能失效(尤其在 Flex 容器中)
不要给截断容器设
同时又用 JS 动态修改
——这会导致 Safari 渲染异常
“展开更多”按钮的 DOM 结构与状态管理
按钮不能只是个
,它必须和被截断内容形成明确的控制关系,否则屏幕阅读器无法感知,键盘用户也无法逻辑聚焦。
立即学习
“
前端免费学习笔记(深入)
”;
推荐结构:
使用HTML,CSS,JavaScript开发Android应用程序 英文文字pdf版附源文件
如果你了解HTML,CSS和JavaScript,您已经拥有所需的工具开发Android应用程序。本动手本书展示了如何使用这些开源web标准设计和建造,可适应任何Android设备的应用程序 - 无需使用Java。您将学习如何创建一个在您选择的平台的Android友好的网络应用程序,然后转换与自由PhoneGap框架到一个原生的Android应用程序。了解为什么设备无关的移动应用是未来的潮流,并开始构建应用程序,提供更
下载
用
显式标记当前状态(
/
)
用
指向目标内容 ID,建立语义关联
按钮文案随状态切换:
→
,避免纯图标(无文字图标需加
)
不要把按钮放在截断容器内部——展开后按钮可能被新内容遮挡或错位
平滑展开动画与高度过渡的兼容写法
直接从
→
有风险:若内容实际高度超过
,动画会卡顿;若设固定值(如
),响应式下易溢出或留白。
更稳妥的做法:
展开时先移除
限制,强制重排,再读取
将该高度设为
并触发 CSS 过渡(transition on
)
收起时设回截断样式,并清空内联
,让 CSS 规则接管
务必监听
而非定时器,避免动画未完成就操作 DOM
iOS Safari 和旧版 Android 的降级处理
在 iOS 15.3 及更早版本存在 bug:当父容器
动态变化时,截断行数会错乱;部分 Android WebView(如 UC 内核)完全不支持。
检测并降级的要点:
用
判断原生支持
降级方案不推荐 JS 截字符串(中英文混排、emoji、全角标点会崩),应改用
+
配合固定行高估算
对不支持的环境,可直接默认展开,或加一个轻量级 JS 库如
(注意其已停止维护,建议 fork 后修复 SSR 问题)
所有降级路径都必须保留
和焦点管理,不能牺牲可访问性换效果
最常被忽略的是:展开/收起后,焦点没回到按钮或内容首行,导致键盘用户迷失上下文。每次状态变更后,显式调用
或
是必要的。
display: noneline-clampmax-heightaria-expandedline-clampline-clampdisplay: -webkit-box-webkit-line-clampline-clampheightmax-heightoverflow: hiddenheight.text-clamp {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; /* 截断为 3 行 */
overflow: hidden;
max-height: calc(1.5em * 3 + 0.25em); /* 行高 × 行数 + 容错余量 */
}aria-expanded"true""false"aria-controls"展开更多""收起"aria-label
这里是多行文本……
max-height: 0max-height: 100vh100vh500pxmax-heightscrollHeightmax-heightmax-heightmax-heighttransitionendbutton.addEventListener('click', () => {
const content = document.getElementById('content');
const expanded = button.getAttribute('aria-expanded') === 'true';
if (expanded) {
content.classList.add('text-clamp');
button.setAttribute('aria-expanded', 'false');
// 移除内联 max-height,交还给 CSS
content.style.maxHeight = '';
} else {
// 先取消截断类,获取真实高度
content.classList.remove('text-clamp');
const height = content.scrollHeight + 'px';
content.style.maxHeight = height;
button.setAttribute('aria-expanded', 'true');
}
});
line-clampfont-sizeCSS.supports('-webkit-line-clamp', '3')max-heightoverflow: hiddenclamp.jsaria-expandedbutton.focus()content.focus({ preventScroll: true })