js中 onload 事件的用法

前言

做表单图片上传业务的时候,经常有这样的需求:预览图片加载完成后,立刻做表单校验,判断用户是否真正上传了图片,还是停留在默认占位图 。很多同学第一反应就是给 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>标签:

  1. img 设置src,浏览器发起请求,图片下载解析完成,触发load回调;
  2. ⚠️如果图片已经缓存,浏览器直接从缓存读取,这时候 load 事件有可能在绑定事件之前就执行完毕!后面绑定的 on ('load') 不会触发!
  3. 如果图片加载失败(404、地址错误),load不会执行,触发error事件;
  4. 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();

关键点:

  1. off('load')先解绑,避免事件堆积;
  2. $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 的场景

  1. 需要获取图片宽高,必须等图片解码完成;
  2. src 动态改变,需要等待新图片资源下载完成后执行业务;
  3. 图片预览渲染完成之后做 UI 调整。

❌不适合过度依赖 load 的场景

  1. 只需要读取src字符串,不需要图片解码信息,完全可以直接拿到 src 就做校验;
  2. 页面初始化就存在图片,缓存图片,很容易出现事件不触发;
  3. 表单提交校验,优先在提交的时候统一校验,不要完全依赖资源事件。

拓展小知识点

  1. complete:原生 img DOM 属性,布尔值,图片加载完毕(缓存 / 网络加载成功)为 true。
  2. load vs ready:
    • load:全部资源(图片、样式)加载完成;
    • $(document).ready():DOM 树解析完成,图片资源不一定下载完。
  3. jQuery on('load')绑定 img,本质封装原生addEventListener('load')。

总结

  1. img 标签load事件,资源加载成功才触发,缓存图片容易出现绑定后不触发,一定要搭配.complete做兼容;
  2. 使用前off()解绑旧事件,防止重复绑定,回调多次执行;
  3. load不冒泡,不能事件委托;
  4. 记得监听error捕获图片加载失败异常;
  5. 如果仅仅校验 src 字符串,不需要图片尺寸,业务上可以直接调用校验函数,不完全依赖 load 事件,减少 bug。
相关推荐
chenbingjie_c3 小时前
C++ 进阶核心知识点总结:模板、内存分区、new/delete、内存池
开发语言·c++
福兮说3 小时前
前端下载大文件,点了按钮半天没反应:fetch + blob 的三个问题和三种替代写法
前端·javascript·性能优化·文件下载
Escalating_xu4 小时前
【C 语言】深入理解指针(1·下):指针运算、野指针、assert 与传址实战
java·c语言·开发语言
实心儿儿4 小时前
Qt — Qt 事件
开发语言·qt
周杰偷奶茶4 小时前
【Java】数据类型与变量
java·开发语言
code斗4 小时前
Java数据结构:堆详解
java·开发语言·数据结构
重生之小比特5 小时前
【C++进阶】map和set
java·开发语言·c++
lie..5 小时前
30天从零开始学AI应用开发(Day 12):像产品一样稳:流式输出、报错重试、花钱控制
开发语言·人工智能·php
傻啦嘿哟5 小时前
Python的默认参数把我坑惨了,原来写[]和写None的区别这么大
开发语言·python·机器学习
DeepAgent6 小时前
AI Agent 工程实践(49):一次真实优化——从 Agent v1 到 v2
开发语言·人工智能·agent