返回博客

van-dialog 弹窗里 textarea 文字诡异跳行?罪魁是 scale 动画而非样式

在某个 C 端微信小程序里,我们有一处弹窗让用户填写备注——弹窗内嵌了一个 textarea,placeholder 是一段两行的提示文字。上线后用户陆续反馈:"弹窗一打开,文字闪了一下,然后才正常"。复现很稳定:弹窗弹出的瞬间,placeholder 先显示两行,紧接着 100ms 内自动折叠成一行,像是被"压缩"了一下。

起初我们把嫌疑人锁定在 placeholder-styleauto-height、字体加载……连续改了五轮,每次都以为找到了,结果症状依旧。

现象

  • van-dialog 打开瞬间,内嵌 textarea 的 placeholder 按"多行"排版
  • 约 50~300 ms 后,文字自动重排为"更少行"(两行变一行、换行位置跳变)
  • 仅在弹窗打开动画播放期间发生,动画结束后文字稳定
  • 调整 placeholder-style、去掉 auto-height、改字号 —— 均无效

根因

vant-weappvan-dialog 有一个默认 prop:

// miniprogram_npm/@vant/weapp/dialog/index.js:64
transition: { type: String, value: 'scale' }

这个值被原样透传给底层 van-popup

<!-- miniprogram_npm/@vant/weapp/dialog/index.wxml -->
<van-popup transition="{{ transition }}" ... />

scale 过渡的 CSS 是 transform: scale(0.7)transform: scale(1)。问题就出在这里。

textareainputpickermapcanvasvideo 都是微信的原生组件(native component),由客户端在独立的渲染层绘制,不完全跟随父容器的 CSS transform

具体时序如下:

  1. 弹窗打开,父容器处于 scale(0.7) 状态
  2. 原生组件 textarea 按此时父容器的视觉宽度(缩小后的)计算内部文字换行位置 → 行数偏多
  3. scale 动画完成,父容器回到 scale(1),原生组件拿到真实宽度,重新换行 → 跳字
坑:transform 动画 + native 组件 = 首帧宽度错位

只要弹窗用的是任何 transform 类动画(scale、rotate、translate3d),内嵌的原生组件就会按动画中间状态的"错误宽度"计算文字布局,动画结束后触发重排,视觉上就是跳字。

这和 placeholder-styleauto-height、字体加载毫无关系,单改它们永远无效。

正解

van-dialogtransition 改为 "fade"fade 只改变 opacity,没有 transform,原生组件从第一帧就能拿到最终宽度,文字不会发生重排。

<van-dialog
  use-slot
  show="{{ showDialog }}"
  transition="fade"
  title="填写备注"
  confirm-button-text="确认"
  bind:confirm="onConfirm"
  bind:cancel="onCancel"
>
  <textarea
    value="{{ remark }}"
    placeholder="请输入备注内容,最多 100 字"
    placeholder-style="font-size: 28rpx; line-height: 1.6; color: #ccc;"
    style="height: 180rpx;"
    bindinput="onInput"
  />
</van-dialog>

另外两个配套修复,消除其他次要跳字因素:

1. placeholder-style.textarea wxss 精确对齐

/* page.wxss */
.textarea { font-size: 28rpx; line-height: 1.6; }
placeholder-style="font-size: 28rpx; line-height: 1.6; color: #ccc;"

两者 font-size / line-height 不一致时,placeholder 与实际输入文字的行高不同,聚焦瞬间仍会有微小跳动。

2. 移除 auto-height,改为固定 height

<!-- 不要 -->
<textarea auto-height ... />
 
<!-- 改为 -->
<textarea style="height: 180rpx;" ... />

auto-height 依赖首帧内容决定高度,原生组件首帧宽度不稳时高度同样不稳。

正解:transition="fade" 是唯一根治方案

opacity-only 动画不产生 transform,原生组件首帧宽度准确,文字不重排。所有"transform 类弹窗动画 + native 组件内容"场景通用此方案。

反模式警示

  • 禁止wx:if + setTimeout 延迟挂载 textarea "跳过"动画期。根因未消除,只是把重排移到用户看不到的时刻;代价是弹窗打开与 textarea 出现时机错开,键盘弹起逻辑也会混乱。
  • 禁止textareatransform: translateZ(0) 等硬件加速 hack。原生组件不受 WebView CSS 影响,毫无效果。

同类场景一并适用:van-popupvan-action-sheet、自定义弹窗只要用了 scale/rotate/translate3d 动画,内含 textarea/input/picker/map 均有此坑。

一句话外卖

弹窗内原生组件跳字,先查弹窗的 transition 是否含 transform,改成 fade(opacity-only)是唯一根治;调文字样式五轮不如读一行 vendor 源码。