业务背景与痛点
作为前端开发,你一定遇到过这样的场景: 业务侧丢过来一个包含大几十项字段的巨型表单 (常见于后台管理系统、入驻申请、审批流表单)。用户辛辛苦苦填了五分钟,满怀期待地点击页面最底部的"提交"按钮,结果页面毫无反应,就像死机了一样。
其实并不是没反应,而是校验失败的红字提示悄悄出现在了滚动条最上方、屏幕可视区域之外的地方。
此时,用户的内心是崩溃的,因为页面纹丝不动。他们往往以为是网络卡了或者系统 Bug,在反复点击无果后,才被迫滑动鼠标滚轮,在一堆密密麻麻的表单项中玩起了"大家来找茬",意外发现原来是自己某个不起眼的必填项漏填了。
不仅是用户体验极差,更痛苦的是,一旦表单项在 Dialog 或 Drawer 里,原生的滚动还会经常失效,或者被固定的 Header 遮挡错误项。
"点击提交 ➡️ 自动丝滑滚动到第一个错误项 ➡️ 强烈的视觉提示 ➡️ 光标自动聚焦。"
这才是现代 Web 应用该有的表单体验闭环。为了彻底解决这个痛点,我开发了一个开箱即用的 Vue 插件 ------ vue-form-error-focus。
💡 插件亮点
本插件原生兼容 Element Plus (Element UI) 和 Ant Design Vue,只需一行代码,即可赋予你的项目"神级"表单体验。
- 智能滚动容器识别 :无论是 Window 全局滚动,还是身处
Dialog、Modal内部的局部滚动,插件都会顺着 DOM 树自动寻找正确的滚动容器。 - 丰富的视觉动效 (Visual Guide) :内置了
fade(淡入淡出)、shake(左右抖动)、glow(外发光呼吸灯)三种现代动效,并且保证无损还原,绝不破坏你原本的斑马纹或表单背景色。 - 真实焦点闭环 (Real Focus) :不仅滚动和高亮,还能自动寻找内部的
input / textarea并调用.focus(),让光标直接闪烁在错误处,用户滚动停下即可打字修改! - 防遮挡设计 :支持
headerOffset参数,完美避开页面顶部的 Fixed Navbar 遮挡。
🛠️ 安装与全局配置
安装非常简单:
bash
npm install vue-form-error-focus
# 或者
yarn add vue-form-error-focus
在你的 main.js 或 main.ts 中进行全局注册,你可以统一定制公司的表单报错风格:
javascript
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 分支里调用即可。
Vue 3 Composition API (推荐)
直接从包中导入核心函数 scrollToError,由于底层做了 nextTick 处理,你不必担心 Vue 异步渲染导致的 DOM 抓取延迟问题。
vue
<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>
Vue 2 / Options API 兼容
考虑到很多老项目还在维护,插件在 install 时会自动将方法挂载到 Vue 实例上,你可以直接通过 this 调用:
javascript
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。 - Web Animations API :摒弃了臃肿的 CSS Keyframes,直接使用原生的
element.animate()驱动shake和glow动效,性能好且无副作用。 - 防冲突 Focus :在执行
inputEl.focus()时,关键性地传入了{ preventScroll: true },防止了浏览器原生聚焦破坏我们精心计算好的平滑滚动动画。
结语
长表单的体验优化往往体现在这些不起眼的细节里。希望这个小插件能帮你少写几行样板代码,也让你的用户少骂两句街。😂
如果你觉得不错,欢迎点个赞!也欢迎在评论区提出你常用的其他 UI 组件库(比如 Naive UI),后续会考虑加入原生类名支持!