赞
踩
JavaScript如何获取图片和视频的尺寸呢?本文很详细,一步一步来,循序渐进.
方法1:得到图片的src属性,是否可以读到图片的宽高?
方法2:得到图片的DOM元素,是否可以读取图片的宽高?
下面我们一起验证一下吧~
首先我们考虑入参,图片的src跟图片的DOM,接着我们如何读取宽高,先来看看图片的DOM元素有没有什么属性吧
随便搞张图片测试一下
我们看到有四个属性,width,height, naturalHeight, naturalWidth
那到底使用那种属性更合适呢?看看MDN文档怎么说
naturalWidth和naturalHeight—[(MDN文档)](HTMLImageElement.naturalHeight - Web API 接口参考 | MDN (mozilla.org))
width和height—:图像嵌入元素 - HTML(超文本标记语言) | MDN (mozilla.org)
看完之后,还是选择naturalWidth和naturalHeight更合适些
然后开始干
function getImgRealSize(img) { // img是dom元素 if (img instanceof HTMLElement) { // 拥有真实宽高属性的dom元素 if ( img.naturalWidth !== null && img.naturalWidth !== undefined ) { return { naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight } } else { // 没有真实宽高属性的dom元素 .... } } else { // img是图片url字符串,通过创建图片dom获取真实宽高 .... } }
如果是一张图片的url,如何读取,我知道有一个Image对象Image() - Web API 接口参考 | MDN (mozilla.org)
**Image()
**函数将会创建一个新的HTMLImageElement
实例。
看到这句话,我就知道怎么读取啦~
const fn = imgSrc => {
if (!(typeof imgSrc === 'string' && !!imgSrc)) {
return reject(new Error('img url not found'))
}
let imgDom = new Image()
imgDom.src = imgSrc
console.log(imgDom.naturalWidth, imgDom.naturalHeight);
console.log(imgDom);
}
咦?怎么回事,脑子快速运转中~~~
图片没有加载好,就已经直接去读取图片的属性啦,所以读到的属性值是0,那为啥console.dir输出的是有值的DOM数据呢?
console.log - Web API 接口参考 | MDN (mozilla.org)
哈哈,console.dir输出的是对象的引用,在控制台输出的时候,已经有DOM值了
那怎么解决呢???
window.onload事件,大家肯定都听过吧,Window - Web API 接口参考 | MDN (mozilla.org)
DOM完全加载完毕,看到这句话,就知道怎么做啦!
当图像装载完毕时调用
const fn = imgSrc => {
if (!(typeof imgSrc === 'string' && !!imgSrc)) {
return reject(new Error('img url not found'))
}
let imgDom = new Image()
imgDom.src = imgSrc
imgDom.onload = () => {
console.log(imgDom.naturalWidth, imgDom.naturalHeight);
}
}
哈哈哈,perfect
一顿操作猛如虎,赶紧试试
const fn = imgSrc => { if (!(typeof imgSrc === 'string' && !!imgSrc)) { return reject(new Error('img url not found')) } let imgDom = new Image() imgDom.src = imgSrc imgDom.onload = () => { return { naturalWidth: imgDom.naturalWidth, naturalHeight: imgDom.naturalHeight } } } function getImgRealSize(img) { // img是dom元素 if (img instanceof HTMLElement) { // 拥有真实宽高属性的dom元素 if ( img.naturalWidth !== null && img.naturalWidth !== undefined ) { return { naturalWidth: img.naturalWidth, naturalHeight: img.naturalHeight } } else { // 没有真实宽高属性的dom元素 fn(img.src) } } else { // img是图片url字符串,通过创建图片dom获取真实宽高 fn(img) } }
结果…额…果然没有这么简单
没事,继续优化啦
首先分析一下为什么会是0???
想想刚刚那个Image对象,难道需要加onload事件,让DOM加载完成
加点辅助的输出,看看怎么回事
控制台输出如下:
哦哦,找到原因啦,开心~
onload事件执行需要时间,是个异步执行,导致同步已经返回了undefined,所以怎么办呢???
大家知道await和async关键字吧, 那肯定知道await就能够等待事件执行完毕,再继续执行后续操作,其实这就是promise微任务加载啦,可以利用promise的resolve和reject解决
const fn = imgSrc => { if (!(typeof imgSrc === 'string' && !!imgSrc)) { return reject(new Error('img url not found')) } let imgDom = new Image() imgDom.src = imgSrc imgDom.onload = () => { return { naturalWidth: imgDom.naturalWidth, naturalHeight: imgDom.naturalHeight } } } function getImgRealSize(img) { return new Promise((resolve, reject) => { const rs = { naturalWidth: 0, naturalHeight: 0 } // img是dom元素 if (img instanceof HTMLElement) { // 拥有真实宽高属性的dom元素 if ( img.naturalWidth !== null && img.naturalWidth !== undefined ) { img.onload = () => { const { naturalWidth, naturalHeight } = img Object.assign(rs, { naturalWidth: naturalWidth, naturalHeight: naturalHeight }) resolve(rs) } } else { // 没有真实宽高属性的dom元素 fn(img.src) } } else { // img是图片url字符串,通过创建图片dom获取真实宽高 fn(img) } }) } // 既然返回了一个promise,那就需要使用.then接收啦 getImgRealSize(document.querySelector("#imgDom")).then(res => { console.log(res, 11) })
这下子就调用成功啦,哈哈
然后看看传入参数是url的时候,会是什么样子吧
没有输出,怎么回事呢,有了上面的经验,大家应该知道了吧,fn函数需要优化,不然就会返回undefined啦,注意fn函数里面需要使用到promise的resolve和reject,所以我们需要把fn写入到promise里面,形成一个闭包环境.
function getImgRealSize(img) { return new Promise((resolve, reject) => { const rs = { naturalWidth: 0, naturalHeight: 0 } const fn = imgSrc => { if (!(typeof imgSrc === 'string' && !!imgSrc)) { return reject(new Error('img url not found')) } let imgDom = new Image() imgDom.src = imgSrc imgDom.onload = () => { const { width, height } = imgDom // 如果imgDom存在naturalWidth属性,那么它的值等于width Object.assign(rs, { naturalWidth: width, naturalHeight: height }) imgDom = null; resolve(rs) } } // img是dom元素 if (img instanceof HTMLElement) { // 拥有真实宽高属性的dom元素 if ( img.naturalWidth !== null && img.naturalWidth !== undefined ) { img.onload = () => { const { naturalWidth, naturalHeight } = img Object.assign(rs, { naturalWidth: naturalWidth, naturalHeight: naturalHeight }) resolve(rs) } } else { // 没有真实宽高属性的dom元素 fn(img.src) } } else { // img是图片url字符串,通过创建图片dom获取真实宽高 fn(img) } }) } getImgRealSize(document.querySelector("#imgDom")).then(res => { console.log(res, 11) }) getImgRealSize("https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cb510bf31a7b4d3692c22b390f157f32~tplv-k3u1fbpfcp-zoom-mark-crop-v2:0:0:1812:480.awebp?").then(res => { console.log(res, 22) })
哈哈,看到返回结果啦,开心到飞起~~~
既然使用了promise,那我们就应该考虑什么时候使用reject呢,所以我们要考虑异常情况哟
我们前面使用到了Image对象的onload事件,但其实除了onload事件,Image也有onerror事件呢?
当图片加载出现异常的时候调用.
所以优化代码如下:
function getImgRealSize(img) { return new Promise((resolve, reject) => { const rs = { naturalWidth: 0, naturalHeight: 0 } const fn = imgSrc => { if (!(typeof imgSrc === 'string' && !!imgSrc)) { return reject(new Error('img url not found')) } let imgDom = new Image() imgDom.src = imgSrc imgDom.onload = () => { const { width, height } = imgDom // 如果imgDom存在naturalWidth属性,那么它的值等于width Object.assign(rs, { naturalWidth: width, naturalHeight: height }) imgDom = null; resolve(rs) } imgDom.onerror = (err) => { imgDom = null; reject(new Error('image onerror')) } } // img是dom元素 if (img instanceof HTMLElement) { // 拥有真实宽高属性的dom元素 if ( img.naturalWidth !== null && img.naturalWidth !== undefined ) { img.onload = () => { const { naturalWidth, naturalHeight } = img Object.assign(rs, { naturalWidth: naturalWidth, naturalHeight: naturalHeight }) resolve(rs) } img.onerror = (err) => { reject(err) } } else { // 没有真实宽高属性的dom元素 fn(img.src) } } else { // img是图片url字符串,通过创建图片dom获取真实宽高 fn(img) } }) }
哈哈,这样就可以了,但是既然都已经做到这里了,我们再多考虑一种情况,我需要上传文件的时候判断图片的尺寸,这里演示就使用elemment-ui的上传组件啦, el-upload
el-upload上传组件的说明: https://element.eleme.cn/#/zh-CN/component/upload
在before-upload这个钩子这里,就能拿到文件对象啦~~~
开干,冲冲冲
// 上传时读到的文件,将文件转为img的src readFiles(file) { return new Promise((resolve, reject) => { let readerFile = new FileReader(); readerFile.onload = (evt) => { getImgRealSize(evt.target.result).then(res => { console.log('img的宽高 -->', res.naturalWidth, res.naturalHeight) if(res.naturalWidth / res.naturalHeight === 16 / 9) { resolve(true); } else { this.$message.error(`直播间只支持16:9比例文件,请重新选择文件`) reject(false) } }); } readerFile.onerror = (err) => { console.error(err); reject(err) } readerFile.readAsDataURL(file); }) }, // 文件上传前格式,大小和数量校验提示 async function beforeAvatarUpload(file) { let isPic = file.type === "image/jpg" || file.type === "image/png" || file.type === "image/jpeg"; if (!isPic) { this.$message.error("不支持选择的上传格式,请重新选择上传格式为jpg或png") return false; } else if (file.size > this.exceedSize) { this.$message.error("只允许上传1M以内的文件") return false; } else if (this.imgfileList.length > this.limitNum) { this.$message.error(`文件数量已到限制`) return false; } else { return await this.readFiles(file) } return false; }
看到了吗?调用了我们上面的getImgRealSize函数,首先我们拿到的是一个文件对象,如果是一张图片的话,直接是拿不到图片的url的,所以我们需要使用readFiles方法,取出图片的url.
就是我们需要使用FileReader对象去读取文件,然后使用readAsDataURL事件,读取到evt.target.result,这个就是图片的url了.
这里需要注意onload事件哟,相信经过上面的经历,并不需要再提醒了.
使用了await函数呢,最好使用try…catch包裹一下await函数,因为这样可以捕获异常处理,如果await中的promise出现了问题,也能够及时把问题抛出来,同时又兼容不阻塞当前流程
使用try…catch…包裹await之后,又出现了新的问题
猜猜下面输入一张不是16:9的图片,会出现什么情况?
效果如下:没有拦截成功,还是上传了图片,很明显,走的是catch,但是err没有输出
why?try/catch 能捕获所有异常,并且把reject当成了异常.
知道原因了,那我们是不是可以直接将reject(false)变成resolve(false)呢?
突然发现使用了try…catch…竟然使得程序不正确了,按理说不应该阻塞上传流程了吗?
为此我们再仔细看看element-ui官网(element-ui官网)对el-upload组件的描述
为此写成reject(false)没啥问题,但是加上try…catch…之后就不正常了
这该如何是好???
返回了false,但就是正常上传了,没有停止,所以反方向想想,直接返回false,是不是不能停止上传了
此刻,是不是发现什么问题了.
没关系滴!一番恍然大悟,有async和await的函数,默认变成了promise函数,需要返回Promise.reject才可以停止上传咯!
那显然改了beforeAvatarUpload方法还不够,还需要改readFiles方法,因为这里resolve(false)显然不能停止上传了,我们需要reject(false),不然就得在beforeAvatarUpload方法中继续判断,如果返回false就需要return为Promise.reject()
最后完整代码如下:
// 上传时读到的文件,将文件转为img的src readFiles(file) { return new Promise((resolve, reject) => { let readerFile = new FileReader(); readerFile.onload = (evt) => { getImgRealSize(evt.target.result).then(res => { console.log('img的宽高 -->', res.naturalWidth, res.naturalHeight) if(res.naturalWidth / res.naturalHeight === 16 / 9) { resolve(true); } else { this.$message.error(`直播间只支持16:9比例文件,请重新选择文件`) reject(`直播间只支持16:9比例文件,请重新选择文件`) } }); } readerFile.onerror = (err) => { console.error(err); reject(err) } readerFile.readAsDataURL(file); }) }, // 文件上传前格式,大小和数量校验提示 async beforeAvatarUpload(file) { let isPic = file.type === "image/jpg" || file.type === "image/png" || file.type === "image/jpeg"; if (!isPic) { this.$message.error("不支持选择的上传格式,请重新选择上传格式为jpg或png") return Promise.reject(); } else if (file.size > this.exceedSize) { this.$message.error("只允许上传1M以内的文件") return Promise.reject(); } else if (this.imgfileList.length > this.limitNum) { this.$message.error(`文件数量已到限制`) return Promise.reject(); } else { try { return await this.readFiles(file) } catch(err) { console.error(err) return Promise.reject(); } } return Promise.reject(); }, 可以封装起来,变成公共方法 // 获取图片原始的真实宽高 // img参数:是图片dom元素或者图片url export function getImgRealSize(img) { return new Promise((resolve, reject) => { const rs = { naturalWidth: 0, naturalHeight: 0 } const fn = imgSrc => { if (!(typeof imgSrc === 'string' && !!imgSrc)) { return reject(new Error('img url not found')) } let imgDom = new Image() imgDom.src = imgSrc imgDom.onload = () => { const { width, height } = imgDom // 如果imgDom存在naturalWidth属性,那么它的值等于width Object.assign(rs, { naturalWidth: width, naturalHeight: height }) imgDom = null; resolve(rs) } imgDom.onerror = (err) => { imgDom = null; reject(new Error('image onerror')) } } // img是dom元素 if (img instanceof HTMLElement) { // 拥有真实宽高属性的dom元素 if ( img.naturalWidth !== null && img.naturalWidth !== undefined ) { img.onload = () => { const { naturalWidth, naturalHeight } = img Object.assign(rs, { naturalWidth: naturalWidth, naturalHeight: naturalHeight }) resolve(rs) } img.onerror = (err) => { reject(err) } } else { // 没有真实宽高属性的dom元素 fn(img.src) } } else { // img是图片url字符串,通过创建图片dom获取真实宽高 fn(img) } }) }
16/9 = 1.777777777…
// 判断视频宽高比例是否为16比9 export function getVideoRealSize(video) { return new Promise((resolve, reject) => { const url = URL.createObjectURL(file) let video = document.createElement('video') video.onloadedmetadata = evt => { // Revoke when you don't need the url any more to release any reference URL.revokeObjectURL(url) console.log('video的宽高 -->', video.videoWidth, video.videoHeight) if(video.videoWidth / video.videoHeight === 16 / 9) { video = null; resolve(true); } else { video = null; this.$message.error(`图片比例需要为16 比 9`) reject(false) } } video.onerror = (err) => { console.error(err); video = null; reject(err) } video.src = url video.load() // fetches metadata }) }
注意:
node --trace-warnings ...
to show where the warning was created,这个报错会导致在catch中的输出阻塞,但是catch中的错误还是会抛出来。// 捕获异步中的错误1 const asyncFn1 = async () => { try { let result1 = await fn(false, 'hello') console.log('中间内容输出') let result2 = await fn(false, 'world') console.log('result1' + result1) console.log('result2' + result2) } catch (error) { console.log('catch:' + error) // 执行 } } asyncFn1(); // catch:fail:hello /* // 捕获异步中的错误2 const asyncFn2 = async () => { try { await fn(false, 'hello').then(() => {}).catch(err => { console.log('result1:' + err) }) console.log('中间内容输出') await fn(false, 'world').then(() => { }).catch(err => { console.log('result2:' + err) }) } catch (error) { console.log('catch:' + error) } } asyncFn2(); result1:fail:hello 中间内容输出 result2:fail:world */
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。