不能直接用 customElements.define 创建滑动容器,因为自定义元素只是“壳”,不具备 scrollWidth、scrollLeft 等滚动能力,也无法可靠代理 scroll 事件;必须在 Shadow DOM 中嵌入真实滚动容器(如 div),并正确配置样式、touch-action、事件处理及对齐逻辑。

为什么不能直接用 customElements.define 造个滑动容器
浏览器不认 <slide-container> 是滚动容器——它没 scrollWidth、没 scrollLeft、scrollBy 调用会静默失败。更麻烦的是,你无法把原生滚动事件(比如 scroll、scrollend)可靠地代理出去,touchstart 里也拿不到真实视口坐标。本质问题:自定义元素是“壳”,不是“滚动上下文”。
必须把原生滚动容器塞进 Shadow DOM 里
想封装又不丢滚动能力,唯一办法是让 <slide-container> 内部包含一个真实的 <div> 并设 overflow-x: auto 或 overflow-x: hidden + 手动 scrollLeft 控制。这个 div 必须在 Shadow DOM 中,否则样式隔离失效,外部 CSS 可能意外撑开或截断滚动区域。
- Shadow DOM 中的容器要显式设
display: flex和white-space: nowrap,子项用flex: 0 0 100vw(全屏页)或固定宽度(卡片页) - 必须加
scroll-behavior: smooth到该容器上,否则scrollBy没动画 -
scrollLeft读取值在缩放下会带小数,每次计算目标位置前先Math.round(container.scrollLeft) - 监听
scroll事件时,用requestAnimationFrame节流,不然 iOS Safari 容易触发多次对齐逻辑
touch 事件必须在 Shadow DOM 内捕获并 preventDefault
Shadow DOM 不会自动继承外部 touch-action 设置,也不转发 touchstart 到内部滚动容器。如果你在自定义元素的 connectedCallback 里只给宿主元素加 touch-action: pan-y,手指一碰就触发页面缩放或纵向滚动——因为横向滚动权限没真正落到滚动容器上。
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 在 Shadow DOM 的滚动容器上直接设
touch-action: pan-y -
touchstart回调第一行必须写e.preventDefault(),且不能加条件判断(iOS 会忽略延迟阻止) - 记录
e.touches[0].pageX,不是clientX——后者不含页面已滚动偏移,拖拽会跳变 -
touchmove中实时算水平位移差,仅当Math.abs(dx) > Math.abs(dy) * 1.5时才更新transform或scrollLeft,防误触
对齐逻辑和防抖必须由组件自己管理
原生滚动没有“滑到哪一页”的概念,scrollLeft 是连续值。你要靠四舍五入后的值判断当前停在哪一页,再决定是否触发 slidechange 自定义事件。但用户松手瞬间常伴随微小回弹,直接读 scrollLeft 容易误判。
立即学习“前端免费学习笔记(深入)”;
- 在
scrollend或touchend后加 100ms 防抖 flag,避免松手后滚动惯性触发二次对齐 - 目标位置按页宽计算:
target = Math.round(scrollLeft / pageWidth) * pageWidth,再scrollTo({ left: target, behavior: 'smooth' }) - 别依赖
scroll-behavior: smooth的回调——scrollend在 Safari 上支持不稳,建议用requestAnimationFrame轮询检测scrollLeft是否稳定 - 对外暴露
goTo(pageIndex)方法时,内部要用scrollTo而非直接赋值scrollLeft,否则失去平滑动画
真正难的不是写几行 transform,而是让滚动行为在 Shadow DOM 里既受控又不失原生手感。很多封装失败,是因为把自定义元素当成了“黑盒”,忘了它里面还得塞一个能呼吸、能响应、能被浏览器识别的真滚动容器。


















