pupuk / blog

My New Blog. Record & Share. Focus on PHP, MySQL, Javascript and Golang.
MIT License
9 stars 2 forks source link

Javascript 判断页面中图片是否加载完成 #1

Open pupuk opened 6 years ago

pupuk commented 6 years ago

某清关业务需要打印一票货物对应的身份证、面单、小票,也就是说一票货物有3张对应的图片。原来一张图片一张纸,有些浪费纸张。

现在希望能把身份证(正反面的拼接照)和面单、购物小票 打印在一张A4纸上。但遇到一个问题,身份证的拼接照有左右拼接,和上下拼接两种方式,不同用户上传的身份证拼接方式不一样。由于我们打算把身份证照片打印在竖着A4纸的左边,就需要对横拼接的身份证图片旋转90度。

于是引申出,等该页面身份证图片加载完成后,(如果某图片没有没有加载完成,则js获取到的宽高是0),判断图片的宽,高,如果width > height,则旋转。

而我们经常使用jQuery就会习惯如下用法

$(document).ready(function(){
  // code
});

但是jQuery的ready是:文档的DOM结构加载完(并不包含html中的src引用其他资源,如图片,视频),就视为ready事件生效。

当然window对象自己也有onload事件,js的window.onload只是文档和所包含的资源全部加载完成,如果页面中图片比较多,比较大,可能很久时间,几秒钟之后,才能触发window.onload事件。

当然可以只对身份证图片监听load时间,如

$('.idcard img').load(function(){
  // code
});

似乎能解决问题,每张身份证图片加载完成后,获取宽高,比较,决定是否用CSS3 transform属性旋转。不过这个方案也有个问题,当刷新这个页面,图片会从浏览器的缓存中读取,不会触发load事件。如果考虑用html属性或者某种方式来强制浏览器不从cache中读取图片,我觉得不是一个好想法。一是没有简单的一句话就能强迫浏览器不走缓存,二是也违背了浏览时做缓存功能,优化体验的初衷。

所幸dom元素还有complete事件,可以获取整个图片,开始遍历。如果img.complete 则返回宽高,否则设置onload事件,等加载完成返回宽高,代码如下

$('img').each(function(){
  if($(this).complete){
     // code
  } else{
    $(this).onload = function(){
       // code
    }
  }
});

在这个过程中的我的思考

$('img').each(function(){
  let width = $(this).width(); //这种方式获取的是图片经过css调整后,显示的宽度,并不是图片原始宽度
 // 当然在此页面我们的css也是同比例缩放的,获取显示宽高一样ok
});

想获取图片的真实宽高,可以获取该图片的src,然后重新创建另外一个不可见的图片对象,将src属性赋值给这个不可见图片,然后再获取不可见图片的宽高,也是一种巧妙的办法,代码如:

// Get on screen image
var screenImage = $("#image");

// Create new offscreen image to test
var theImage = new Image();
theImage.src = screenImage.attr("src");

// Get accurate measurements from that.
var imageWidth = theImage.width;
var imageHeight = theImage.height;

当然站在架构的层面来看,可以后台在输出的时候,就把图片的真实宽高,抛给前端。这无疑是更科学的方式。

判断整个页面中的图片是否加载完成,github是已经有了开源实现,功能也比较丰富。如果觉得引入整个项目,文件太大,也有些功能并不需要,开发者可以阅读源码,选择自己需要功能,进行改造。 https://github.com/desandro/imagesloaded/

参考资料: https://zhuanlan.zhihu.com/p/34181416 https://css-tricks.com/snippets/jquery/get-an-images-native-width/

pupuk commented 6 years ago

跟朋友分享的时候,有朋友就获取图片真实宽高,提出了如果将原图的src值取出,赋值给一个新建的不可见img对象,会不会新开一个network请求,会多开HTTP请求。个人觉得浏览器的策略,对于同一个图片地址,浏览器会根据Etag和Last-Modified和自身的缓存机制来决定,一般不会重复多开HTTP请求。

为此我们在Chrome浏览器环境下做如下测试

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body onload="load()">
    <img src="http://pic1.win4000.com/pic/a/e1/7eebfcb75b.jpg" alt="">
</body>
<script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

<script>
    //整个文档包含图片 加载完成
    function load(){
        var src = $("img").attr("src");

        //hidden Image1
        var hiddenImage1 = new Image();
        hiddenImage1.src = src;
        console.log(1);

        //hidden Image2
        var hiddenImage2 = new Image();
        hiddenImage2.src = src;
        console.log(2);
    }
</script>
</html>

111

发现并没有除了最初文档加载时,发出了一次图片请求,后来我们创建的hiddenImage1和hiddenImage2 均没有产生新的network请求。这应该是chrome浏览器的策略,我相信也是大部分浏览器的策略。

我们尝试修改下url

hiddenImage1.src = src + '?';
hiddenImage2.src = src + '?';

qq 22222 发先改变了url,就会产生新的network请求。

pupuk commented 6 years ago
hiddenImage1.src = src + '#abc';
hiddenImage2.src = src + '#def';

这种是不会被认为是新的URL,因此也不会产生新的network请求。

3333

为什么?和#在URL中有不同的含义,可以参考这篇文章 https://www.cnblogs.com/kaituorensheng/p/3776527.html

pupuk commented 6 years ago

结果有小伙伴问了:

如果使用src的值,复制给新创建的hiddenImage1,浏览器是没有新开network请求,那还能正确获取图片的宽高不呢?

问题不错,但是仔细想想,虽然没有新开netword请求,但是浏览器还是从缓存中读取了这张图片的,所以可以正确获取原始宽高。

马上实验:

使用CSS将图片缩小,宽度为100px

<style>
        img{width: 100px;}
</style>

获取新image object的宽高

//hidden Image1
var hiddenImage1 = new Image();
hiddenImage1.src = src;
console.log(hiddenImage1.width, hiddenImage1.height);

实验结果 444

探讨了这么多,其实最好的是图片在被上传的时候,宽高就被记录在数据库中,后台输出HTML或者JSON数据的时候,就把真实宽高通过自定义属性或者json字段输出给前端。很多瀑布流的项目也是这么定义的,这也说明前后端沟通和架构的重要性,不然只有贴膏药式的解决方案:joy::joy::joy: