乐趣区

关于nginx:石火电光追风逐日前端优化之次时代图片压缩格式WebP的项目级躬身实践Python3-PILNginx

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_190

咱们晓得,在前端界有一个共识:速度就是生命,带宽就是金钱。怎么将页面加载速度无效晋升是有数前端工程师无时不刻在思考的课题,目前的网络环境中,除了视频,图片仍旧是占用流量较大的一部分,对于 app 端尤其如此,因而,如何在保障图片视觉不失真的前提下放大图片体积,对于节俭带宽和电池电量都非常重要,因而 Google 在十年前提出了一种新的图片压缩格局:WebP,给图片的优化提供了新的方向。

WebP 的劣势在于它具备更优的图像数据压缩算法,在领有肉眼简直无奈辨认差别的图像品质前提下,带来更小的图片体积,同时具备了无损和有损的压缩模式、Alpha 通明以及动画的个性,从 JPEG 和 PNG 上的转化成果都十分突出、稳固和对立,寰球驰名视频网站 YouTube 的视频缩略图采纳 WebP 后,网页加载速度晋升了 10%,晋升成果可见一斑:

本次咱们以本站为例子,应用 Python3 对站内图片进行无损压缩和转换,同时利用 Nginx 针对 Webp 图片判断申请头来对老版本浏览器做向下兼容,实现 Webp 图片的无缝切换。

首先,将网站的图片转换为 Webp 格局,这里咱们应用 PIL 库,该库宽泛用于 Python 中的图像处理,并且 PIL 图像库中最重要的类是 Image 类,该类在模块中以雷同的名称定义。

装置 PIL:

python3 -m pip install --upgrade pip  
python3 -m pip install --upgrade Pillow

能够通过 open 办法加载图像文件并且展现它:

from PIL import Image  
img = Image.open('sample.jpg')  
img.show()

Image.convert() 办法能够返回该图像的转换后的正本。此办法可通过调色板转换像素。以后版本反对“L”,“RGB”和“CMYK”之间的所有格局转换。save(fp,format)应用两个输出参数,第一个是保留转换后的文件的文件门路(fp),第二个是要转换成的文件格式。

转换 JPG 到 PNG:

from PIL import Image  
img = Image.open('sample.jpg').convert('RGB')  
img.save('sample.jpg.png', 'png')

转换 PNG 到 JPG:

from PIL import Image  
img = Image.open('sample.png').convert('RGB')  
img.save('sample.png.jpeg', 'jpeg')

转换 PNG 到 WEBP:

from PIL import Image  
img = Image.open('sample.png').convert('RGB')  
img.save('sample.png.webp', 'webp')

转换 JPG 到 WEBP:

from PIL import Image  
img = Image.open('sample.jpg').convert('RGB')  
img.save('sample.jpg.webp', 'webp')

看起来相当简略,有的时候,咱们可能须要把某个目录下的图片进行批量转换,首先读取所有须要转换的图片:

import os  
  
files = os.listdir('/opt/img')  
  
images = [file for file in files if file.endswith(('jpg','png','jpeg'))]  
  
print(images)

liuyue:mytornado liuyue$ python3 "/Users/liuyue/wodfan/work/mytornado/test_webp.py"  
['qrcode.png', 'touxiang_1.png', 'code.jpeg', 'test.png', 'touxiang1.jpg', 'logo_dark.png', 'logo.png', 'touxiang.png', 'code1.jpeg']  
liuyue:mytornado liuyue$ 

随后建设转换方法:

def convert_image(image_path, image_type):  
  
    im = Image.open(image_path)  
    print(image_path)  
    im = im.convert('RGB')  
    image_name = image_path.split('.')[0]  
    print(f"This is the image name: {image_name}")  
  
    if not os.path.exists(f"{image_path}.webp"):  
  
        if image_type == 'jpg' or image_type == 'png' or image_type == 'jpeg':  
            im.save(f"{image_name}.{image_type}.webp", 'webp')  
        else:  
            raise Error

这里咱们将转换后的正本对立加上后缀.webp

之后进行转换操作:

for image in images:  
    if image.endswith('jpg'):  
        convert_image(image, image_type='jpg')  
    elif image.endswith('jpeg'):  
        convert_image(image, image_type='jpg')  
    elif image.endswith('png'):  
        convert_image(image, image_type='png')  
    else:  
        raise Error

这里须要留神的是,是在原图文件名根底上退出后缀.webp,而不是笼罩原图后缀,这样前面替换图片内容时会更加不便。

接下来的课题就是怎么判断客户端的浏览器是否反对 Webp 格局的图片,目前 ios 和新版的 Safari 浏览器曾经对 webp 进行了适配,然而对于老版本的零碎和浏览器怎么向下兼容是一个令人头疼的问题。

能够在前端通过 js 脚本来进行判断:

window.isSupportWebp = false;// 是否反对  
(function() {var img = new Image();   
    function getResult(event) {// 如果进入加载且图片宽度为 1( 通过图片宽度值判断图片是否能够显示)  
        window.isSupportWebp = event && event.type === 'load' ? img.width == 1 : false;  
    }  
    img.onerror = getResult;  
    img.onload = getResult;  
    img.src = 'data:image/webp;base64,UklGRiQAAABXRUJQVlA4IBgAAAAwAQCdASoBAAEAAwA0JaQAA3AA/vuUAAA='; // 一像素图片  
})();  
  
console.log(window.isSupportWebp);  
true

原理就是加载一像素的 webp 判断是否显示胜利,如果 window.isSupportWebp 为 true 咱们就能够将 webp 后缀加载否则就加载原后缀的图片,然而基于前端的解决方案须要批改大量的代码,同时如果判断业务逻辑放在页面里无形中也减少了页面累赘,有没有办法在不变动代码逻辑的前提下,能够主动切换图片后缀呢?答案就在后端的 Nginx。

咱们晓得浏览器的每个申请头中都带有 ”Accept” 字段, 例如:

Accept:image/webp,image/apng,image/*,*/*;q=0.8

此时通过 nginx 对 Accept 进行判断,如果带有 webp,阐明该浏览器反对 webp,咱们就由后端加载 webp,如果头部没有 webp 字样,阐明浏览器不反对,此时 nginx 持续加载原后缀文件,这也就是为什么之前在图片转换过程中要保留原始图片文件的起因。

首先关上 nginx 的 mime.types 文件,查看 nginx 是否配置 webp,如果没有须要手动加上:

vim /etc/nginx/mime.types

能够看到全副文件类型:

types {  
    text/html                                        html htm shtml;  
    text/css                                         css;  
    text/xml                                         xml;  
    image/gif                                        gif;  
    image/jpeg                                       jpeg jpg;  
    application/javascript                           js;  
    application/atom+xml                             atom;  
    application/rss+xml                              rss;  
  
    text/mathml                                      mml;  
    text/plain                                       txt;  
    text/vnd.sun.j2me.app-descriptor                 jad;  
    text/vnd.wap.wml                                 wml;  
    text/x-component                                 htc;  
  
    image/png                                        png;  
    image/svg+xml                                    svg svgz;  
    image/tiff                                       tif tiff;  
    image/vnd.wap.wbmp                               wbmp;  
    image/webp                                       webp;  
    image/x-icon                                     ico;  
    image/x-jng                                      jng;  
"/etc/nginx/mime.types" 97L, 5231C                            22,5         顶端  
    application/octet-stream                         deb;  
    application/octet-stream                         dmg;  
    application/octet-stream                         iso img;  
    application/octet-stream                         msi msp msm;  
  
    audio/midi                                       mid midi kar;  
    audio/mpeg                                       mp3;  
    audio/ogg                                        ogg;  
    audio/x-m4a                                      m4a;  
    audio/x-realaudio                                ra;  
  
    video/3gpp                                       3gpp 3gp;  
    video/mp2t                                       ts;  
    video/mp4                                        mp4;  
    video/mpeg                                       mpeg mpg;  
    video/quicktime                                  mov;  
    video/webm                                       webm;  
    video/x-flv                                      flv;  
    video/x-m4v                                      m4v;  
    video/x-mng                                      mng;  
    video/x-ms-asf                                   asx asf;  
    video/x-ms-wmv                                   wmv;  
    video/x-msvideo                                  avi;  
}

次要查看有没有 webp,如果没有进行增加:

image/webp                                       webp;

随后批改主配置文件:

vim /etc/nginx/nginx.conf

在 http 配置中加上 webp 文件的判断逻辑:

map $http_accept $webp_suffix {default   "";"~*webp"".webp";}

同时在 server 中配置逻辑,如果反对就将头部信息替换为 webp 后缀文件:

location ~* ^/v3u/Public/images/.+\.(png|jpe?g)$ {  
  add_header Vary Accept;  
  try_files $uri$webp_suffix $uri =404;  
}

残缺配置如下:

user root;  
worker_processes auto;  
error_log /var/log/nginx/error.log;  
pid /run/nginx.pid;  
  
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.  
include /usr/share/nginx/modules/*.conf;  
  
events {worker_connections 1024;}  
  
http {log_format  main  '$remote_addr - $remote_user [$time_local]"$request"''$status $body_bytes_sent "$http_referer" ''"$http_user_agent""$http_x_forwarded_for"';  
  
    access_log  /var/log/nginx/access.log  main;  
  
    map $http_accept $webp_suffix {default   "";"~*webp"".webp";}  
  
    sendfile            on;  
    tcp_nopush          on;  
    tcp_nodelay         on;  
    keepalive_timeout   65;  
    types_hash_max_size 2048;  
  
    include             /etc/nginx/mime.types;  
    default_type        application/octet-stream;  
  
        gzip on;  
        gzip_min_length  1k;  
        gzip_buffers     4 16k;  
        gzip_http_version 1.0;  
        gzip_comp_level 2;  
        gzip_types       text/plain application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png;  
        gzip_vary on;  
        gzip_disable msie6;  
  
    open_file_cache max=1000 inactive=20s;  
    open_file_cache_valid 30s;  
    open_file_cache_min_uses 5;  
    open_file_cache_errors off;  
  
    # Load modular configuration files from the /etc/nginx/conf.d directory.  
    # See http://nginx.org/en/docs/ngx_core_module.html#include  
    # for more information.  
    include /etc/nginx/conf.d/*.conf;  
  
    server {location ~* ^/v3u/Public/images/.+\.(png|jpe?g)$ {  
  add_header Vary Accept;  
  try_files $uri$webp_suffix $uri =404;  
}  
  
        include /etc/nginx/default.d/*.conf;  
  
        location / { }  
  
  
        error_page 404 /404.html;  
            location = /40x.html { }  
  
        error_page 500 502 503 504 /50x.html;  
            location = /50x.html {}}  
  
#include vhost/*.conf;  
}

批改好配置文件之后,先不要焦急重启服务器,检测一下配置文件语法:

[root@iz2ze0ndt5s9wq2s6ff8g6z nginx]# nginx -t  
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok  
nginx: configuration file /etc/nginx/nginx.conf test is successful  
[root@iz2ze0ndt5s9wq2s6ff8g6z nginx]#

如果没有问题,重启服务:

systemctl restart nginx.service

当初让咱们来测试一下,以本站的 logo 图片为例子,如果是不反对 webp 格局的浏览器,比方低版本的 Safari(13.0.3):

能够看到加载图片的类型放弃了原图片后缀:png

当初换一个浏览器,应用反对 webp 的 chrome(88.0.4324):

能够看到曾经主动切换为 webp 格局了,让咱们再次通过 google 的页面性能打分工具 PageSpeedInsights 对本站进行评测:https://developers.google.com…

一望而知,页面加载速度失去了晋升,并且在 Google 的页面优化倡议中,曾经达成了采纳新一代格局提供图片的要求。

结语:当然了,其实 Nginx 是能够对图片进行实时压缩的,然而那样须要独自装置模块以及其余服务的染指,这对于低版本服务器无疑是要消耗老本的,所以本计划就是通过 python3 脚本提前将图片转换好,再用 nginx 判断一下即可,仅仅一个脚本 + 两行配置文件即可实现系统升级,看起来此计划是最佳抉择,既没有前端代码侵入,也不须要各种简单的配置和服务搭建。置信在不远的未来,基于 google 开源的 VP8 视频编码格局的 WebM 视频也将会大面积的代替传统的 mp4 格局,前端架构的性能优化,始终是业界亘古不变的课题之一。

原文转载自「刘悦的技术博客」https://v3u.cn/a_id_190

退出移动版