前言
做表单图片上传业务的时候,经常有这样的需求:预览图片加载完成后,立刻做表单校验,判断用户是否真正上传了图片,还是停留在默认占位图 。很多同学第一反应就是给 img 标签绑定load事件。
load事件本身作用:当图片(或 DOM 对象)完成加载(资源加载完毕)之后触发回调函数 。 但是这个事件有非常多隐蔽坑点,如果不注意,会出现校验失效、事件不触发、多次触发、页面刷新不执行等 bug。本文结合真实图片上传校验业务代码,讲解load事件用法、业务场景、踩坑点以及解决方案。
技术栈:jQuery + HTML 图片上传预览 + 表单自定义校验
业务原始代码
业务场景:图片上传之后,#single_pic_show这个 img 标签 src 会被替换成新图片地址;如果没有上传,src 是默认占位图pic_upload.png。 我们想在图片加载完成之后,马上执行校验函数,判断当前是默认占位图,还是用户真实上传的图片。
①绑定 load 事件监听
// 图片上传验证
$('#single_pic_show').on('load', function () {
validator.form.img_upload_volid($('#single_pic_show'));
});
②校验函数实现 img_upload_volid
function img_upload_volid(element) {
// 获取当前图片上传的图片路径
let img_url = element.attr('src');
// 判断当前路径是否是默认图片
if (img_url.includes('pic_upload.png')) {
showHint(element, '请上传图片', 0);
is_valid = false;
} else {
hideHint(element);
is_valid = true;
}
}
逻辑看上去没问题:图片加载完毕 → 触发 load 事件 → 执行校验函数,读取 src,区分默认图和上传图。但是实际运行会遇到一堆坑。
img load 事件原理
load事件:资源加载完成才触发 ,只在资源加载成功完成后执行。 对于<img>标签:
- img 设置
src,浏览器发起请求,图片下载解析完成,触发load回调;- ⚠️如果图片已经缓存,浏览器直接从缓存读取,这时候 load 事件有可能在绑定事件之前就执行完毕!后面绑定的 on ('load') 不会触发!
- 如果图片加载失败(404、地址错误),
load不会执行,触发error事件;- load 事件不会冒泡,不能做全局事件委托!
业务代码现存问题分析
坑 1:页面初始化默认图片,load 事件有可能不执行
页面刚打开,img 就已经有 src(默认占位图 pic_upload.png)。 图片来自浏览器缓存,图片加载完成发生在$().on('load')绑定之前。
现象:页面刚进来,校验不执行,提示消失,表单直接认为图片合法。只有手动更换图片之后校验才生效。
原因:资源已经加载完毕,load 事件已经过去,后面再绑定监听,回调不会跑。
坑 2:多次更换图片,load 事件多次累加绑定
如果上传组件反复切换图片,每次 DOM 刷新,重复执行on('load'),事件会重复绑定,校验函数会跑很多次,造成提示文字闪烁、is_valid变量乱跳。
坑 3:load 事件不会捕获加载失败场景
如果上传返回图片地址错误、图片 404,load不会触发。此时校验逻辑完全不会执行,表单会误以为图片正常。
坑 4:不能用事件委托
// ❌错误写法,load不冒泡,委托无效
$(document).on('load','#single_pic_show',function(){});
load属于资源事件,不向上冒泡,事件委托写法完全失效。
改良方案
方案 1:缓存兼容写法(最常用)
思路:绑定 load 事件之后,手动判断图片是否已经完成加载,如果已经 complete,直接手动执行回调。 img DOM对象.complete属性:原生属性,true 代表图片已经加载完毕(缓存加载完成也算)。
// 图片上传验证
function bindImgValidate(){
let $img = $('#single_pic_show');
// 先解绑旧事件,防止重复绑定
$img.off('load').on('load', function () {
validator.form.img_upload_volid($img);
});
// 兼容缓存:图片已经加载完成,手动调用一次校验
if($img[0].complete){
validator.form.img_upload_volid($img);
}
}
bindImgValidate();
关键点:
off('load')先解绑,避免事件堆积;$img[0].complete访问原生 DOM 属性,判断图片是否已经加载完毕,如果缓存命中,直接执行校验,不用等待 load;
方案 2:同时监听 load + error,捕获图片异常
图片地址错误 404 的时候,也要标记校验不通过。
function bindImgValidate(){
let $img = $('#single_pic_show');
$img.off('load error')
.on('load', function () {
validator.form.img_upload_volid($img);
})
.on('error',function(){
// 图片加载失败,直接校验失败
showHint($img,'图片加载异常,请重新上传',0);
is_valid = false;
});
if($img[0].complete){
validator.form.img_upload_volid($img);
}
}
方案 3:上传修改 src 之后再调用校验(业务折中方案)
很多业务中,图片 src 是上传成功之后 js 代码手动赋值。 在修改 src 的那行代码之后,直接调用校验函数,不完全依赖 load 事件。
// 上传成功回调中修改预览图
$('#single_pic_show').attr('src',res.url);
// 修改完直接校验,再依靠load做二次校验
validator.form.img_upload_volid($('#single_pic_show'));
适合业务:自己 js 控制修改 src,不是页面静态渲染。缺点:图片还没解码完成就校验,但是我们校验的只是 src 字符串,不需要图片宽高,完全够用。
load 事件适用场景与不适用场景总结
✅适合用 load 的场景
- 需要获取图片宽高,必须等图片解码完成;
- src 动态改变,需要等待新图片资源下载完成后执行业务;
- 图片预览渲染完成之后做 UI 调整。
❌不适合过度依赖 load 的场景
- 只需要读取
src字符串,不需要图片解码信息,完全可以直接拿到 src 就做校验; - 页面初始化就存在图片,缓存图片,很容易出现事件不触发;
- 表单提交校验,优先在提交的时候统一校验,不要完全依赖资源事件。
拓展小知识点
complete:原生 img DOM 属性,布尔值,图片加载完毕(缓存 / 网络加载成功)为 true。loadvsready:load:全部资源(图片、样式)加载完成;$(document).ready():DOM 树解析完成,图片资源不一定下载完。
- jQuery
on('load')绑定 img,本质封装原生addEventListener('load')。
总结
- img 标签
load事件,资源加载成功才触发,缓存图片容易出现绑定后不触发,一定要搭配.complete做兼容; - 使用前
off()解绑旧事件,防止重复绑定,回调多次执行; load不冒泡,不能事件委托;- 记得监听
error捕获图片加载失败异常; - 如果仅仅校验 src 字符串,不需要图片尺寸,业务上可以直接调用校验函数,不完全依赖 load 事件,减少 bug。