js获取文件大小 window.performance

背景:今天 每周例行技术分享 的时候,有位同事说 js 获取不到文件大小:浏览器默认不允许js 执行IO操作。 举了个例子,比如想获取一个图片地址的文件大小,因为之前做过图片上传功能所以比较敏感,感觉应该是可以的,虽然他说网上找过很多资料但是都不行,但是这种东西 只有自己试试才安心嘛~~

失败方案 canvas.toBlob

function loadImg(url){
    return new Promise((res, rej) => {
        const img = new Image()
        img.crossOrigin = ''   // 防止img 画在canvas上时报跨域错误
        img.onload = function() {
            res(this)
        }
        img.onerror = function() {
            rej()
        }
        img.src = url
    })
}

async function getImgSize(url) {
    const urlArr = url.split('.')
    const mime = "image/" + urlArr[urlArr.length-1]
    const img = await loadImg(url)
    const {width,height} = img
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(img,0,0,width,height)
    canvas.toBlob(
        blob => {
            console.log(url,blob,(blob.size / 1000)+'KB')
        },
        mime
    );
}

getImgSize('https://images.daojia.com/changsha/banner/pic/25675a8d10bfa08e9599d2deb9a44eae.jpeg')

看日志输出

看着成了,但实际.... 与 本地 和 Network 差 十万八千里


我尝试了几个图片,每个图片都比之前要大,而且增大程度不一定,这块估计是canvas画了一遍的原因。。。

失败方法 canvas.toDataURL + dataURLtoBlob

function loadImg(url){
    return new Promise((res, rej) => {
        const img = new Image()
        img.crossOrigin = ''   // 防止img 画在canvas上时报跨域错误
        img.onload = function() {
            res(this)
        }
        img.onerror = function() {
            rej()
        }
        img.src = url
    })
}

async function getImgSize(url) {
    const urlArr = url.split('.')
    const mime = "image/" + urlArr[urlArr.length-1]
    const img = await loadImg(url)
    const {width,height} = img
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d');
    canvas.width = width;
    canvas.height = height;
    ctx.drawImage(img,0,0,width,height);
    const blob =  dataURLtoBlob(canvas.toDataURL(mime));
    console.log(url,blob,(blob.size / 1000)+'KB')
}
function dataURLtoBlob(dataurl) {
    var arr = dataurl.split(',');
    var _arr = arr[1].substring(0,arr[1].length-2);
    var mime = arr[0].match(/:(.*?);/)[1],
        bstr =atob(_arr),
        n = bstr.length,
        u8arr = new Uint8Array(n);
    while (n--) {
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new Blob([u8arr], {
        type: mime
    });
}

getImgSize('https://images.daojia.com/changsha/banner/pic/25675a8d10bfa08e9599d2deb9a44eae.jpeg')

看结果⬇️

。。。看来不能走canvas这条路了

成功方案 performance


难道真的没有办法了嘛?我盯着 浏览器Network 陷入沉思, 突然 Network 资源加载 的size在我眼里 越来越抢眼,貌似有个api可以获取到资源加载~~~~~ 走起

function getImgSize(url){
    return new Promise((res, rej) => {
        var performance = window.performance || 
            window.msPerformance || 
            window.webkitPerformance;
        if (performance) {
            const img = new Image()
            img.crossOrigin = ''
            img.onload = function() {
                let [imgEntries] = performance.getEntriesByName(url)
                res(imgEntries)
            }
            img.onerror = function() {
                rej('图片加载失败')
            }
            img.src = url
        }else{
            rej('浏览器不持支持performance')
        }
    })
}
let url = 'https://images.daojia.com/changsha/banner/pic/25675a8d10bfa08e9599d2deb9a44eae.jpeg'
getImgSize(url).then(v=>{
    console.log(v)
    console.log(v.decodedBodySize)
    console.log((v.decodedBodySize / 1000),'KB')
    // transferSize  判断是否缓存
})

这块使用 decodedBodySize,因为 css js等资源 decodedBodySize 与本地大小一致

decodedBodySize-MDN文档
encodedBodySize-MDN 文档
performance.getEntriesByName-MDN文档
哈哈哈哈,成了~

多组数据验证

图片地址 本地size canvas.toBlob canvas.toDataURL + dataURLtoBlob performance
https://images.daojia.com/changsha/banner/pic/25675a8d10bfa08e9599d2deb9a44eae.jpeg 20519 36345 36343 20519
https://images.daojia.com/dop/custom/dda52b42212b6bdea3e12d03ac97ffbe.png 65511 164303 164302 65511
https://images.daojia.com/dop/custom/f8f0528c61e243699c309f5f25eab513.png 14638 36394 36391 14638

没有看错 performance 与本地的完全一样

不限制于图片

function loadJSFile(source) {
    return new Promise((res, rej) => {
        const node = document.createElement('script')
        node.async = false
        node.src = source

        const supportOnload = 'onload' in node
        if (supportOnload) {
            node.onload = function(){
                res(this)
            }
            node.onerror = () => {
                rej(`${source}加载失败`)
            }
        } else {
            node.onreadystatechange = function(){
                if (/loaded|complete/i.test(node.readyState)) {
                    res(this)
                }
            }
        }
        document.getElementsByTagName('head')[0].appendChild(node)
    })
}
function loadImgFile(url){
    return new Promise((res, rej) => {
        const img = new Image()
        img.crossOrigin = ''
        img.onload = function() {
            res(this)
        }
        img.onerror = function() {
            rej('图片加载失败')
        }
        img.src = url
    })
}

function loadCSSFile(source) {
    return new Promise((res, rej) => {
        var head = document.getElementsByTagName('head')[0];
        var link = document.createElement('link');
        link.type = 'text/css';
        link.rel = 'stylesheet';
        link.href = source;
        const supportOnload = 'onload' in link
        if (supportOnload) {
            link.onload = function(){
                res(this)
            }
            link.onerror = () => {
                rej(`${source}加载失败`)
            }
        } else {
            link.onreadystatechange = function(){
                if (/loaded|complete/i.test(link.readyState)) {
                    res(this)
                }
            }
        }
        head.appendChild(link);
    })
}
function getFileSize(url){
    return new Promise((res, rej) => {
    var performance = window.performance || 
        window.msPerformance || 
        window.webkitPerformance;
    if (performance) {
        let [imgEntries] = performance.getEntriesByName(url)
        if(imgEntries){
            console.log(imgEntries)
            console.log(imgEntries.decodedBodySize)
            console.log((imgEntries.decodedBodySize / 1000),'KB')
            res(imgEntries)
        }else{
            rej('资源加载有问题')
        }
    }else{
        rej('浏览器不持支持performance')
    }
})
}
(async function (){
    const css = 'https://cdn.bootcdn.net/ajax/libs/twitter-bootstrap/4.4.1/css/bootstrap.min.css';
    await loadCSSFile(css)
    await getFileSize(css)

    const js = 'https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js';
    await loadJSFile(js)
    await getFileSize(js)

    const img = 'https://images.daojia.com/dop/custom/f8f0528c61e243699c309f5f25eab513.png';
    await loadImgFile(img)
    await getFileSize(img)
})()

兼容性

最后记得使用caniuse - performance查看一下兼容性