Stories

Detail Return Return

石火電光追風逐日|前端優化之次時代圖片壓縮格式WebP的項目級躬身實踐(Python3 PIL+Nginx) - Stories Detail

原文轉載自「劉悦的技術博客」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

user avatar alibabawenyujishu Avatar guochenglong Avatar damonxiaozhi Avatar 6fafa Avatar yuzhihui Avatar Z-HarOld Avatar joe235 Avatar haixiudezhusun Avatar weishiledanhe Avatar xiao2 Avatar best-doraemon Avatar prosuoqi Avatar
Favorites 47 users favorite the story!
Favorites

Add a new Comments

Some HTML is okay.