上传头像预览仅隔次生效的代码问题排查及优化咨询
问题根因
- 最核心的问题是图片加载是异步操作,当前代码的逻辑执行顺序完全错误:
- 给
img.src赋值后,浏览器会异步加载图片资源,img.onload回调要等图片加载完成才会触发 - 你在赋值
img.src后立刻调用toDataURL,此时图片还没加载完,drawImage还未执行,canvas是空的,所以第一次拿到的是空白内容 - 第二次触发上传时,上一次的图片可能因为缓存已经加载完成,才会把上一次的内容画到canvas里,所以出现“隔一次生效”的假象
- 给
- 其他次要问题:
- 每次创建canvas后用
document.querySelector("canvas")全局查找元素,如果页面有其他canvas会获取到错误的对象 - 还没等
drawImage执行就提前移除了canvas,后续onload触发时已经找不到canvas上下文 - 没有释放
createObjectURL创建的内存地址,长期使用会导致内存泄漏 createObjectURL仅支持接收File对象,原代码中无文件时传入默认头像字符串的写法会直接报错
- 每次创建canvas后用
修复方案
最小改动修复版(保留canvas逻辑)
如果需要提前对头像做裁剪、压缩处理,保留canvas逻辑的修复版本如下,所有依赖图片加载完成的逻辑全部移到img.onload回调内部:
$("#pfp").on("change", e => { const file = e.target.files[0]; // 直接持有创建的canvas实例,不要全局查询 const canvas = $('<canvas width="150" height="150" style="display: none;">').appendTo("body")[0]; const ctx = canvas.getContext("2d"); const img = new Image(); // 未选择文件时直接显示默认头像 if (!file) { $("#pfp-img").attr("src", "https://www.media.yoo-babobo.com/images/user.png"); canvas.remove(); return; } const objUrl = window.URL.createObjectURL(file); img.onload = function() { // 图片加载完成后再操作canvas、更新预览 ctx.drawImage(img, 0, 0, 150, 150); const dataUri = canvas.toDataURL("image/png"); document.getElementById("pfp-img").src = dataUri; // 操作完成后再移除canvas、释放内存 canvas.remove(); window.URL.revokeObjectURL(objUrl); } img.src = objUrl; })
更优简化版(无canvas,性能更高)
如果仅需要预览上传的图片,不需要提前做裁剪、压缩处理,完全不需要用canvas做中转,直接把生成的ObjectURL赋值给预览图即可,逻辑更简单、性能损耗更低:
$("#pfp").on("change", e => { const file = e.target.files[0]; const $pfpImg = $("#pfp-img"); // 释放之前的objectURL避免内存泄漏 const prevUrl = $pfpImg.attr("src"); if (prevUrl.startsWith("blob:")) window.URL.revokeObjectURL(prevUrl); if (!file) { // 未选择文件时恢复默认头像 $pfpImg.attr("src", "https://www.media.yoo-babobo.com/images/user.png"); return; } const objUrl = window.URL.createObjectURL(file); $pfpImg.attr("src", objUrl); })
内容的提问来源于stack exchange,提问作者The Yule
相关产品推荐
相关产品推荐

