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。
相关推荐
志尊宝1 小时前
Vue3 零基础每日笔记(005):ref 响应式基础——为什么 script 里要 .value
前端·javascript·vue.js·笔记
xiancai_xianyu1 小时前
想把数据模型一次建完美再上AI?会一直卡在建模里
开发语言·人工智能·php·数据模型·语义层·本体建模·企业知识网络
(Charon)1 小时前
【C++】网络缓冲区设计(二):Ring Buffer环形缓冲区、head/tail与跨界读写
开发语言·c++
Java后端的Ai之路2 小时前
LangChain Deep Agents 从入门到企业实战
开发语言·人工智能·python·langchain·deepagents
会飞的拖把2 小时前
Python文件操作详解:从文件读写到os、shutil模块实战
开发语言·python
RS迷途小书童2 小时前
Python 解析大疆无人机 SRT 字幕日志
开发语言·python·无人机
知兀2 小时前
框架脚手架搭建,推送github一键使用
javascript·vue.js·ecmascript
点心的游戏开发世界2 小时前
GDScript 入门笔记(十六):文件读写与数据持久化
开发语言·笔记·游戏引擎·godot
会飞的拖把2 小时前
Python序列详解:列表、元组、字符串的通用操作(超详细版)
开发语言·windows·python