前言
大家好,我是阿林。
前段时间,产品在验收时给我们加了一个需求,用户提交表单时,要滚动定位到校验出错的地方,就像这样:
我说:做这个应该会比较麻烦,主要是涉及到多个子表单组件通信,还要计算元素在页面的位置,还要写动画,大概 8 工时,要不不做了?
她柳眉皱起,考虑了很久,道:不行!用户体验至关重要,你尽力做嘛,多预估点开发时间就行。
我面露难色,很勉强地说:好吧,哎,那就上线时间往后面延一天吧,我尽量把这个功能做出来。
项目经理知道要加这个需求后,说:好,你加油!就延后一天上线吧,
我心道:小样儿,给我加需求,我一小时就做完的东西,骗你们说八小时你还真信,又多一天时间学(mo)习(yu)了,耶!
需求分析
这种需求主要是用在超级长的表单,长到超过一个页面。
要实现这个功能需要用到一个 Web API,Element.scrollIntoView
Element
接口的scrollIntoView()方法会滚动元素的父容器,使被调用scrollIntoView()的元素对用户可见。
很显然,我们只需要在表单提交校验不通过时,找到整个页面上第一个校验出错的元素,然后调这个元素的 scrollIntoView
方法,就能实现这个功能。
scrollIntoView 这个 api 帮我们把计算元素位置和动画的事情做了,但是怎么找到页面上第一个校验出错的元素呢?
以 element-ui
的 el-form
组件为例,打开控制台发现,校验出错时元素会被加上 is-error
class,很显然,我们可以调用元素的 getElementsByClassName
方法,找到所有校验出错的元素,再取第一个就行。
现在就差最后一个问题了,多个子组件表单和父组件的通信如何实现?
vue 开发中,表单的校验一般是写到子组件的 validate 方法上,然后在父组件上给子组件定义 ref,然后通过
this.$refs
调用子组件的 validate 方法(element-ui就是这么做的)。
代码实现
以上面 gif 图 为例,假设这个页面有五个子表单组件。
父组件:
<ComponentA ref="componentAEle" />
<ComponentB ref="componentBEle" />
<ComponentC ref="componentCEle" />
<ComponentD ref="componentDEle" />
<ComponentE ref="componentEEle" />
<button @click="validate">
async validate() {
const validRefs = [
this.$refs.componentAEle,
this.$refs.componentBEle,
this.$refs.componentCEle,
this.$refs.componentDEle,
this.$refs.componentEEle
]
for (const i in validRefs) {
const res = await validRefs[i]?.validate()
if (!res) {
return false
}
}
return true
}
复制代码
子组件,以 componentA 为例,其他几个子组件同理:
<el-form ref="componentAFormEle">
...
</el-form>
validate() {
return new Promise(resolve => {
this.$refs.componentAFormEle.validate(async valid => {
if (!valid) {
await this.$nextTick()
this.$utils.scrollToError(this.$refs.componentAFormEle.$el)
}
resolve(valid)
})
})
}
复制代码
utils.js 封装的scrollToError方法
/**
* 自动滚动到错误的校验框
*
* @param {*} el 包裹的元素
* @param {string} [scrollOption={
* behavior: 'smooth',
* block: 'center'
* }] 滚动参数
*/
const scrollToError = (
el,
scrollOption = {
behavior: 'smooth',
block: 'center'
}
) => {
const isError = el.getElementsByClassName('is-error')
isError[0].scrollIntoView(scrollOption)
}
复制代码
这里有一点要特别注意,调用子组件的 validate 方法时,一定要先调一次 this.$nextTick()
,不然的话就会报错:
因为页面的is-error元素还没出现,程序就运行到 scrollIntoView 方法那里去了,所以才会出现 undefined
错误。所以要调一下 this.$nextTick()
,等这一轮视图更新好了,再调后续的 scrollToError 方法。
总结
这种写法非常不vue,vue推荐的是,不到迫不得已,尽量不要写原生 DOM 事件,但我感觉我已经到迫不得已的情况了,如果你有更好的实现方法,一定要在评论区告诉我~
另外
前言除了产品验收完给我们加需求是真的,其他都是我编的~
现实是:
产品经理:这么简单的功能,哪来那么多理由?这个需求必须做。
项目经理:这么个小需求要开发8工时?不可能,晚上加个班,给我实现,临时加点小需求就不算工时了。
项目经理:上线时间延期一天?你在做梦,上线时间提前了算正常,延后了就扣绩效。
...
hhh,生活不易,阿林叹气,但是还是要保持乐观,努力学习呀!
希望我的文章能对前端路上的你有帮助。