FitCloud
136M · 2026-04-15
作为前端开发,你一定遇到过这样的场景: 业务侧丢过来一个包含大几十项字段的巨型表单(常见于后台管理系统、入驻申请、审批流表单)。用户辛辛苦苦填了五分钟,满怀期待地点击页面最底部的“提交”按钮,结果页面毫无反应,就像死机了一样。
其实并不是没反应,而是校验失败的红字提示悄悄出现在了滚动条最上方、屏幕可视区域之外的地方。
此时,用户的内心是崩溃的,因为页面纹丝不动。他们往往以为是网络卡了或者系统 Bug,在反复点击无果后,才被迫滑动鼠标滚轮,在一堆密密麻麻的表单项中玩起了“大家来找茬”,意外发现原来是自己某个不起眼的必填项漏填了。
不仅是用户体验极差,更痛苦的是,一旦表单项在 Dialog 或 Drawer 里,原生的滚动还会经常失效,或者被固定的 Header 遮挡错误项。
“点击提交 ️ 自动丝滑滚动到第一个错误项 ️ 强烈的视觉提示 ️ 光标自动聚焦。”
这才是现代 Web 应用该有的表单体验闭环。为了彻底解决这个痛点,我开发了一个开箱即用的 Vue 插件 —— vue-form-error-focus。
本插件原生兼容 Element Plus (Element UI) 和 Ant Design Vue,只需一行代码,即可赋予你的项目“神级”表单体验。
Dialog、Modal 内部的局部滚动,插件都会顺着 DOM 树自动寻找正确的滚动容器。fade(淡入淡出)、shake(左右抖动)、glow(外发光呼吸灯)三种现代动效,并且保证无损还原,绝不破坏你原本的斑马纹或表单背景色。input / textarea 并调用 .focus(),让光标直接闪烁在错误处,用户滚动停下即可打字修改!headerOffset 参数,完美避开页面顶部的 Fixed Navbar 遮挡。安装非常简单:
npm install vue-form-error-focus
# 或者
yarn add vue-form-error-focus
在你的 main.js 或 main.ts 中进行全局注册,你可以统一定制公司的表单报错风格:
import { createApp } from 'vue'
import App from './App.vue'
import FormErrorFocusPlugin from 'vue-form-error-focus'
const app = createApp(App)
// 注册插件,并配置全局动效
app.use(FormErrorFocusPlugin, {
headerOffset: 60, // 顶部偏移量,防止固定导航栏遮挡
behavior: 'smooth', // 丝滑的滚动体验
highlight: true, // 开启视觉高亮反馈
animationType: 'shake', // 可选: 'fade' | 'shake' | 'glow',强烈推荐 shake 抖动!
duration: 600, // 动效持续时间
autoFocus: true // 滚动到达后,输入框自动获取焦点
})
app.mount('#app')
配置完成后,在你的业务代码中,只需要在表单校验失败的 else 分支里调用即可。
直接从包中导入核心函数 scrollToError,由于底层做了 nextTick 处理,你不必担心 Vue 异步渲染导致的 DOM 抓取延迟问题。
<template>
<el-form ref="formRef" :model="form" :rules="rules">
<!-- 你的巨型表单内容 -->
<el-button type="primary" @click="submitForm">立即提交</el-button>
</el-form>
</template>
<script setup>
import { ref } from 'vue'
// 直接引入核心滚动函数
import { scrollToError } from 'vue-form-error-focus'
const formRef = ref(null)
const submitForm = async () => {
if (!formRef.value) return
await formRef.value.validate((valid) => {
if (valid) {
console.log('校验通过,提交请求!')
} else {
// 核心代码:校验失败,一行代码搞定滚动+抖动+聚焦!
scrollToError()
// 当然,你也可以在这里覆盖全局配置
// scrollToError({ animationType: 'glow', duration: 1000 })
}
})
}
</script>
考虑到很多老项目还在维护,插件在 install 时会自动将方法挂载到 Vue 实例上,你可以直接通过 this 调用:
export default {
methods: {
submitForm() {
this.$refs.formRef.validate((valid) => {
if (!valid) {
// Options API 调用方式
this.$scrollToError()
return false
}
})
}
}
}
很多同学好奇是怎么做到兼容各种 UI 库的,核心源码其实非常轻量。主要做了以下几步:
document.querySelector('.el-form-item.is-error, .ant-form-item-has-error') 抓取第一个报错节点。closest('.el-dialog__body, .ant-modal-body') 判断是否在弹窗内,从而决定是 window.scrollTo 还是局部容器的 scrollTo。element.animate() 驱动 shake 和 glow 动效,性能好且无副作用。inputEl.focus() 时,关键性地传入了 { preventScroll: true },防止了浏览器原生聚焦破坏我们精心计算好的平滑滚动动画。长表单的体验优化往往体现在这些不起眼的细节里。希望这个小插件能帮你少写几行样板代码,也让你的用户少骂两句街。
如果你觉得不错,欢迎点个赞!也欢迎在评论区提出你常用的其他 UI 组件库(比如 Naive UI),后续会考虑加入原生类名支持!