别再让JS耽误你进步了。

44 阅读3分钟

1. 需求说明

某个阳光明媚的下午,作者正沉浸在摸鱼的氛围中,工作通讯图标闪烁—----得,来活了。设计认为两个下载按钮宽度不一致,有失美观,需要改成一样宽,宽度以最宽的那个按钮为准

页面上有两个结构相似的按钮(例如「For PC」和「For mobile device」),希望:

  • 视觉一致:两个按钮整体宽度相同;
  • 随文案变化:中英文切换时仍尽量稳定;

这不简单 js一把梭哈 十分钟搞定 不要耽误摸鱼。

2. JS 方案:测量 DOM 再写回宽度

  1. ref 指向按钮容器;
  2. nextTickquerySelectorAll('.inner-left')(或整颗 .btn-item);
  3. getBoundingClientRect().width / offsetWidth 取最大值;
  4. 通过内联 style 或 CSS 变量(如 --btn-left-width)写回;
  5. resizelocale(i18n)变化时再次同步。

脚本示意:

const syncButtonWidth = async () => {
  syncedWidth.value = 0
  await nextTick()

  const nodes = containerRef.value?.querySelectorAll('.inner-left')
  if (!nodes?.length) return

  const maxW = Math.ceil(
    [...nodes].reduce((m, el) => Math.max(m, el.getBoundingClientRect().width), 0)
  )
  syncedWidth.value = maxW
}

模板与变量:

<div class="btn-container" ref="containerRef" :style="buttonWidthStyle">
const buttonWidthStyle = computed(() =>
  syncedWidth.value ? { '--btn-left-width': `${syncedWidth.value}px` } : {}
)

样式:

.inner-left {
  width: var(--btn-left-width, auto);
}

提交、部署、上线……

很快哈发现:改变浏览器窗口大小时,较宽的按钮会出现闪动和意外换行——不对,这里有坑。经作者抽丝剥茧(实际是问了「豆包」),遂发现:

2.2 JS 方案的问题

问题说明
闪动常见写法会先清空宽度再测量再写入,中间经历多帧布局,用户会看到宽度或换行突变。
resize 抖动window.resize 高频触发时反复改 CSS 变量,易造成布局反复计算(layout thrashing)。
与换行强耦合锁的是某一帧的像素宽;字体子像素、rem、父级 max-width 变化时,可能与真实排版不一致,出现意外换行或空白。
响应式冲突窄屏要缩、宽屏要放时,仍按历史最大宽度锁死,往往需要更多断点与清理逻辑。

3. 纯 CSS 方案:用布局表达「同宽」

核心:不要用像素去「抄」浏览器算好的结果,而用 flex / grid 在规则上保证同宽。

3.1 H5:纵向 inline-flex + stretch

两个按钮上下排列时,用列方向的 flex,在交叉轴(水平)上把子项拉满容器宽度;容器宽度由较宽那一行决定,两行自然同宽。

.btn-container {
  display: inline-flex;
  flex-direction: column;
  align-items: stretch;
  gap: 20px;
  max-width: calc(100vw - 40px);
  box-sizing: border-box;
}

.btn-item-wrapper {
  display: block;
  width: 100%;
}

.btn-item {
  display: flex;
  width: 100%;
  min-width: 0;
  /* 结构:inner-left | divider | inner-right */
}

原理简述:

  • 竖排时交叉轴为水平方向,stretch 使每个 wrapper 与容器同宽;
  • inline-flex 使容器水平方向收缩为「包住这一列」的宽度,该宽度由较宽的一行决定;
  • 因此两行按钮整体同宽

行内左侧文案区可用 flex: 1; min-width: 0 占据除竖线、右侧区域外的空间。文案可选用:

span {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}

避免窄屏异常折行;过长用省略号,比锁死像素宽更稳。


3.2 PC:横向 grid 两列等分 + max-content

两个按钮左右并排时,用 1fr 1fr 保证两列始终等宽;用 width: max-content 让整行宽度贴近内容需求,max-width: 100% 防止超出视口。

.btn-container {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  width: max-content;
  max-width: 100%;
  box-sizing: border-box;
}

.btn-item {
  width: 100%;
  min-width: 0;
  display: flex;
}

.inner-left {
  flex: 1;
  min-width: 0;
  justify-content: center;
}

width: max-contentmax-width: 100% 的作用:

  • max-content:容器宽度按内容所需的「合适」宽度收缩,不会无故拉满整屏;
  • max-width: 100%:再宽也不能超过父级,窄视口时整行被压缩,两列仍等分

4. 方案对照小结

维度JS 量宽纯 CSS(flex / grid)
实现复杂度ref、nextTick、监听较多主要在样式
resize / 语言切换易闪、多帧布局随排版一次计算
与换行固定像素易不一致nowrap / ellipsis 或自然换行由 CSS 统一
可维护性样式与脚本双处修改改样式即改布局

完美!!!