減小HTTP請求之合併圖片詳解(大型網站優化技術)

  1、相關知識講解php

  看過雅虎的前端優化35條建議,都知道優化前端是有多麼重要。頁面的加載速度直接影響到用戶的體驗。80%的終端用戶響應時間都花在了前端上,其中大部分時間都在下載頁面上的各類組件:圖片,樣式表,腳本,Flash等等。css

  減小組件數必然可以減小頁面提交的HTTP請求數。這是讓頁面更快的關鍵。減小頁面組件數的一種方式是簡化頁面設計。但有沒有一種方法能夠在構建複雜的頁面同時加快響應時間呢?嗯,確實有魚和熊掌兼得的辦法。html

  這裏咱們就拿雅虎的第一條建議:儘可能減小HTTP請求數裏的減小圖片請求數量 進行講解。前端

  咱們都知道,一個網站的一個頁面可能有不少小圖標,例如一些按鈕、箭頭等等。當加載html文檔時,只要遇到有圖片的,都會自動創建起HTTP請求下載,而後將圖片下載到頁面上,這些小圖片可能也就是十幾K大甚至1K都不到,假如咱們的一個頁面有一百個小圖標,咱們在加載頁面時,就要發送100個HTTP請求,若是你的網站訪問量很大併發量也很高,假如上百萬訪問量,那發起的請求就是千萬級別了,服務器是有必定的壓力的,而且一個用戶的一個頁面要發起那麼多請求,是很耗時的。web

  因此,咱們優化的方案就是:將這些十幾K、幾K的小圖標合併在一張圖片裏,而後用CSS的background-imagebackground-position屬性來定位要顯示的部分。瀏覽器

  、代碼實現服務器

  一、思路:數據結構

    將一個文件夾裏的圖標,自動生成在一張圖片裏面,同時自動生成對應的css文件,咱們只要在HTML裏的標籤中添加相應的屬性值就能顯示圖片了。併發

  二、實現過程:前端優化

  1 <?php
  2     //本身定義一個根目錄
  3     define('ROOT', $_SERVER['DOCUMENT_ROOT'].'iconwww');
  4     //這個是圖片的目錄
  5     define('RES_BASE_URL', 'http://localhost:8080/iconwww/img');
  6 
  7     /**
  8      * 生成背景圖的函數
  9      */
 10     function generateIcon() {
 11         //網站根目錄
 12         $webRoot = rtrim(ROOT, '/');
 13         //背景圖目錄
 14         $root = "$webRoot/img/bg";
 15         //Php-SPL庫中 的 目錄文件遍歷器
 16         $iterator = new DirectoryIterator($root);
 17         //開始遍歷該背景圖目錄下的目錄,咱們是把想生成背景圖的目錄,放在bg目錄中以各個模塊的目錄分類存放
 18         foreach ($iterator as $file) {
 19             //遇到目錄遍歷
 20             if (!$file->isDot() && $file->isDir()) {
 21                 //取得文件名
 22                 $fileName = $file->getFilename();
 23                 generateIconCallback("$root/$fileName", "$webRoot/img/$fileName", "$webRoot/css/$fileName.css");
 24             }
 25         }
 26     }
 27 
 28     /**
 29      * 用戶生成合並的背景圖和css文件的函數
 30      * @param  string $dir         生成背景圖的圖標所在的目錄路徑
 31      * @param  string $bgSavePath  背景圖所保存的路徑
 32      * @param  string $cssSavePath css保存的路徑
 33      */
 34     function generateIconCallback($dir, $bgSavePath, $cssSavePath) {
 35         $shortDir = str_replace('\\', '/', substr($dir, strlen(ROOT-1)));
 36         //返回文件路徑信息
 37         $pathInfo = pathinfo($bgSavePath.'.png');
 38 
 39         $bgSaveDir = $pathInfo['dirname'];
 40         //確保目錄可寫
 41         ensure_writable_dir($bgSaveDir);
 42         //背景圖名字
 43         $bgName = $pathInfo['filename'];
 44         //調用generateIconCallback_GetFileMap()函數生成每個圖標所須要的數據結構
 45         $fileMap = array('a' => generateIconCallback_GetFileMap($dir));
 46 
 47         $iterator = new DirectoryIterator($dir);
 48         foreach ($iterator as $file) {
 49             if ($file->isDot()) continue;
 50             if ($file->isDir()) {
 51                 //二級目錄也要處理
 52                 $fileMap['b-'.$file->getFilename()] = generateIconCallback_GetFileMap($file->getRealPath());
 53             } 
 54         }
 55         ksort($fileMap);
 56 
 57         //分析一邊fileMap,計算整個背景圖的大小和每個圖標的offset
 58         //初始化偏移量和背景圖    
 59         $offsetX = $offsetY = $bgWidth = 0;
 60         //設定每一個小圖標之間的距離
 61         $spaceX =$spaceY = 5;
 62         //圖片最大寬度
 63         $maxWidth = 800;
 64         $fileMd5List =array();
 65         //這裏須要打印下$fileMap就知道它的數據結構了
 66         foreach ($fileMap as $k1 => $innerMap) {
 67             foreach ($innerMap as $k2 => $itemList) {
 68                 //行高姐X軸偏移量初始化
 69                 $offsetX = $lineHeight = 0;
 70                 foreach ($itemList as $k3 => $item) {
 71                     //變量分別是:圖標的寬度,高度,類型,文件名,路徑,MD5加密字符串
 72                     list($imageWidth, $imageHeight, $imageType, $fileName, $filePathname, $fileMd5) = $item;
 73                     $fileMd5List []= $fileMd5;
 74                     //若是圖片的寬度+偏移量 > 最大寬度(800) 那就換行
 75                     if ($offsetX !== 0 && $imageWidth + $offsetX > $maxWidth) {
 76                         $offsetY += $spaceY + $lineHeight;
 77                         $offsetX = $lineHeight = 0;
 78                     }
 79                     //若是圖片高度 > 當前行高  那就講圖片高度付給行高咱們這的
 80                     if ($imageHeight > $lineHeight) $lineHeight = $imageHeight;
 81                     $fileMap[$k1][$k2][$k3] = array($imageWidth, $imageHeight, $offsetX, $offsetY, $imageType, $fileName, $filePathname);
 82                     //X軸偏移量的計算
 83                     $offsetX += $imageWidth + $spaceX;
 84                     if ($offsetX > $bgWidth) $bgWidth = $offsetX;
 85                 }
 86                 //Y軸偏移量的計算
 87                 $offsetY +=  $lineHeight + $spaceY;
 88             }
 89         }
 90         //把右下兩邊多加了的空白距離給幹掉
 91         $bgWidth -= $spaceX;
 92         $bgHeight = $offsetY - $spaceY;
 93         $fileMd5List = implode("\n", $fileMd5List);
 94 
 95         //生成背景圖和 css文件
 96         
 97         //資源路徑
 98         $resBaseUrl = RES_BASE_URL;
 99         $suffix = base_convert(abs(crc32($fileMd5List)), 10, 36);
100         $writeHandle = fopen($cssSavePath, 'w');
101         fwrite($writeHandle, "/** bg in dir: $shortDir/ */\n[icon-$bgName]{background:url({$resBaseUrl}/$bgName.png?$suffix) no-repeat;display:inline-block;}");
102         
103         //作圖片,這些函數具體能夠查看PHP手冊
104         $destResource = imagecreatetruecolor($bgWidth, $bgHeight);
105         imagealphablending($destResource, false);
106         imagesavealpha($destResource, false);
107         $color = imagecolorallocatealpha($destResource, 255, 255, 255, 127);
108 
109         imagefill($destResource, 0, 0, $color);
110 
111         //對每一張小圖片進行處理,生成在大背景圖裏,並生成css文件
112         foreach ($fileMap as $innerMap) {
113             foreach ($innerMap as $itemList) {
114                 foreach ($itemList as $item) {
115                      list($imageWidth, $imageHeight, $offsetX, $offsetY, $imageType, $fileName, $filePathname) = $item;
116                      if ($imageType === IMAGETYPE_PNG) {
117                         $srcResource = imagecreatefrompng($filePathname);
118                      } else if ($imageType === IMAGETYPE_JPEG) {
119                         $srcResource = imagecreatefromjpeg($filePathname);
120                      }
121                      imagecopy($destResource, $srcResource, $offsetX, $offsetY, 0, 0, $imageWidth, $imageHeight);
122                      imagedestroy($srcResource);
123 
124                      //寫入css
125                      $posX = $offsetX === 0 ? 0 : "-{$offsetX}px";
126                      $posY = $offsetY === 0 ? 0 : "-{$offsetY}px";
127                      fwrite($writeHandle, "\n[icon-$bgName=\"$fileName\"]{width:{$imageWidth}px;height:{$imageHeight}px;background-position:$posX $posY;}");
128                  } 
129             }
130         }
131 
132         //壓縮級別 7
133         imagepng($destResource, "$bgSavePath.png", 7);
134         imagedestroy($destResource);
135         fclose($writeHandle);
136 
137         $shortCssSavePath = substr($cssSavePath, strlen(ROOT));
138     }
139 
140     /**
141      * 將圖片的信息處理成咱們想要的數據結構
142      * @param  [type] $dir [description]
143      * @return [type]      [description]
144      */
145     function generateIconCallback_GetFileMap($dir) {
146         $map = $sort = array();
147         $iterator = new DirectoryIterator($dir);
148         foreach($iterator as $file) {
149             if(!$file->isFile()) continue;
150             $filePathname = str_replace("\\", '/', $file->getRealPath());
151             //這些函數能夠查看PHP手冊
152             $imageInfo = getimagesize($filePathname);
153             $imageWidth = $imageInfo[0];
154             $imageHeight = $imageInfo[1];
155             $imageType = $imageInfo[2];
156 
157             if(!in_array($imageType, array(IMAGETYPE_JPEG, IMAGETYPE_PNG))) {
158                 $fileShortName = substr($filePathname, strlen(ROOT) - 1);
159                 echo "<p> $fileShortName 圖片被忽略: 由於圖片類型不是png|jpg.</p>";
160                 continue;
161             }
162 
163             //這是咱們的圖片規格,行高分別有 16 32 64 128 256 99999 
164             foreach(array(16, 32, 64, 128, 256, 99999) as $height) {
165                 if($imageHeight <= $height) {
166                     $mapKey = $height;
167                     break;
168                 }
169             }
170             if(!isset($map[$mapKey])) $map[$mapKey] = array();
171             $filePathInfo = pathinfo($filePathname);
172             $map[$mapKey] []= array($imageWidth, $imageHeight, $imageType, $filePathInfo['filename'], $filePathname, md5_file($filePathname));
173             $sort[$mapKey] []= str_pad($imageHeight, 4, '0', STR_PAD_LEFT) . $filePathInfo['filename'];
174         }
175         foreach($map as $k => $v) array_multisort($map[$k], SORT_ASC, SORT_NUMERIC, $sort[$k]);
176         ksort($map, SORT_NUMERIC);
177         return $map;
178     }
179 
180     /**
181      * 判斷目錄是否可寫
182      * @param  string $dir 目錄路徑
183      */
184     function ensure_writable_dir($dir) {
185         if(!file_exists($dir)) {
186             mkdir($dir, 0766, true);
187             @chmod($dir, 0766);
188             @chmod($dir, 0777);
189         }
190         else if(!is_writable($dir)) {
191             @chmod($dir, 0766);
192             @chmod($dir, 0777);
193             if(!@is_writable($dir)) {
194                 throw new BusinessLogicException("目錄不可寫", $dir);
195             }
196         }
197     }
198 
199     generateIcon();
200 ?>
201 <!DOCTYPE html>
202 <html>
203 <head>
204     <link rel="stylesheet" type="text/css" href="css/Pink.css">
205     <title></title>
206     
207 
208 </head>
209 <body>
210 <div>咱們直接引入所生成的css文件,並測試一下是否成功</div>
211 <br>
212 <div>這裏在span標籤 添加屬性 icon-Pink ,值爲About-40,正常顯示圖片</div>
213 <span icon-Pink="About-40"></span>
214 </body>
215 </html>

 

  調用以上代碼,咱們的瀏覽器是這樣顯示的:

  而後css目錄生成了Pink.css文件:

  

  img目錄下生成了Pink.png文件:

 

  看看生成的背景圖是長啥樣子:

 

  接下來咱們再看一下所生成的圖片大小與Pink文件夾裏全部小圖片總和的大小,對它們作個比較:

  

 

  從上圖能夠看出,咱們生成的圖片的大小明顯小於文件夾全部圖片的大小,因此在將100個小圖標下載下來的速度 會明顯小於 將背景圖下載下來和將CSS下載下來的速度。

  當訪問量大時,或者小圖片的量大時,會起到很明顯的優化效果!!!

  

  代碼中的每個點都基本上有註釋,很方便你們去理解,只要你們用心去看,確定能將這一網站優化技術用到本身的項目中。

  本次博文就寫到這!!!

 

 

  若是此博文中有哪裏講得讓人難以理解,歡迎留言交流,如有講解錯的地方歡迎指出。

  若是您以爲您能在此博文學到了新知識,請爲我頂一個,如文章中有解釋錯的地方,歡迎指出。

  互相學習,共同進步!

相關文章
相關標籤/搜索