一、相關知識講解
看過雅虎的前端優化35條建議,都知道優化前端是有多麼重要。頁面的載入速度直接影響到使用者的體驗。80%的終端使用者響應時間都花在了前端上,其中大部分時間都在下載頁面上的各種元件:圖片,樣式表,指令碼,Flash等等。
減少元件數必然能夠減少頁面提交的HTTP請求數。這是讓頁面更快的關鍵。減少頁面元件數的一種方式是簡化頁面設計。但有沒有一種方法可以在構建複雜的頁面同時加快響應時間呢?嗯,確實有魚和熊掌兼得的辦法。
這裡我們就拿雅虎的第一條建議:儘量減少HTTP請求數裡的減少圖片請求數量 進行講解。
我們都知道,一個網站的一個頁面可能有很多小圖示,例如一些按鈕、箭頭等等。當載入html文件時,只要遇到有圖片的,都會自動建立起HTTP請求下載,然後將圖片下載到頁面上,這些小圖片可能也就是十幾K大甚至1K都不到,假如我們的一個頁面有一百個小圖示,我們在載入頁面時,就要傳送100個HTTP請求,如果你的網站訪問量很大併發量也很高,假如上百萬訪問量,那發起的請求就是千萬級別了,伺服器是有一定的壓力的,並且一個使用者的一個頁面要發起那麼多請求,是很耗時的。
所以,我們優化的方案就是:將這些十幾K、幾K的小圖示合併在一張圖片裡,然後用CSS的background-image
和background-position
屬性來定位要顯示的部分。
二、程式碼實現
1、思路:
將一個資料夾裡的圖示,自動生成在一張圖片裡面,同時自動生成對應的css檔案,我們只要在HTML裡的標籤中新增相應的屬性值就能顯示圖片了。
2、實現過程:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 |
<?php //自己定義一個根目錄 define('ROOT', $_SERVER['DOCUMENT_ROOT'].'iconwww'); //這個是圖片的目錄 define('RES_BASE_URL', 'http://localhost:8080/iconwww/img'); /** * 生成背景圖的函式 */ function generateIcon() { //網站根目錄 $webRoot = rtrim(ROOT, '/'); //背景圖目錄 $root = "$webRoot/img/bg"; //Php-SPL庫中 的 目錄檔案遍歷器 $iterator = new DirectoryIterator($root); //開始遍歷該背景圖目錄下的目錄,我們是把想生成背景圖的目錄,放在bg目錄中以各個模組的目錄分類存放 foreach ($iterator as $file) { //遇到目錄遍歷 if (!$file->isDot() && $file->isDir()) { //取得檔名 $fileName = $file->getFilename(); generateIconCallback("$root/$fileName", "$webRoot/img/$fileName", "$webRoot/css/$fileName.css"); } } } /** * 使用者生成合並的背景圖和css檔案的函式 * @param string $dir 生成背景圖的圖示所在的目錄路徑 * @param string $bgSavePath 背景圖所儲存的路徑 * @param string $cssSavePath css儲存的路徑 */ function generateIconCallback($dir, $bgSavePath, $cssSavePath) { $shortDir = str_replace('\\', '/', substr($dir, strlen(ROOT-1))); //返回檔案路徑資訊 $pathInfo = pathinfo($bgSavePath.'.png'); $bgSaveDir = $pathInfo['dirname']; //確保目錄可寫 ensure_writable_dir($bgSaveDir); //背景圖名字 $bgName = $pathInfo['filename']; //呼叫generateIconCallback_GetFileMap()函式生成每一個圖示所需要的資料結構 $fileMap = array('a' => generateIconCallback_GetFileMap($dir)); $iterator = new DirectoryIterator($dir); foreach ($iterator as $file) { if ($file->isDot()) continue; if ($file->isDir()) { //二級目錄也要處理 $fileMap['b-'.$file->getFilename()] = generateIconCallback_GetFileMap($file->getRealPath()); } } ksort($fileMap); //分析一邊fileMap,計算整個背景圖的大小和每一個圖示的offset //初始化偏移量和背景圖 $offsetX = $offsetY = $bgWidth = 0; //設定每個小圖示之間的距離 $spaceX =$spaceY = 5; //圖片最大寬度 $maxWidth = 800; $fileMd5List =array(); //這裡需要列印下$fileMap就知道它的資料結構了 foreach ($fileMap as $k1 => $innerMap) { foreach ($innerMap as $k2 => $itemList) { //行高姐X軸偏移量初始化 $offsetX = $lineHeight = 0; foreach ($itemList as $k3 => $item) { //變數分別是:圖示的寬度,高度,型別,檔名,路徑,MD5加密字串 list($imageWidth, $imageHeight, $imageType, $fileName, $filePathname, $fileMd5) = $item; $fileMd5List []= $fileMd5; //如果圖片的寬度+偏移量 > 最大寬度(800) 那就換行 if ($offsetX !== 0 && $imageWidth + $offsetX > $maxWidth) { $offsetY += $spaceY + $lineHeight; $offsetX = $lineHeight = 0; } //如果圖片高度 > 當前行高 那就講圖片高度付給行高我們這的 if ($imageHeight > $lineHeight) $lineHeight = $imageHeight; $fileMap[$k1][$k2][$k3] = array($imageWidth, $imageHeight, $offsetX, $offsetY, $imageType, $fileName, $filePathname); //X軸偏移量的計算 $offsetX += $imageWidth + $spaceX; if ($offsetX > $bgWidth) $bgWidth = $offsetX; } //Y軸偏移量的計算 $offsetY += $lineHeight + $spaceY; } } //把右下兩邊多加了的空白距離給幹掉 $bgWidth -= $spaceX; $bgHeight = $offsetY - $spaceY; $fileMd5List = implode("\n", $fileMd5List); //生成背景圖和 css檔案 //資源路徑 $resBaseUrl = RES_BASE_URL; $suffix = base_convert(abs(crc32($fileMd5List)), 10, 36); $writeHandle = fopen($cssSavePath, 'w'); fwrite($writeHandle, "/** bg in dir: $shortDir/ */\n[icon-$bgName]{background:url({$resBaseUrl}/$bgName.png?$suffix) no-repeat;display:inline-block;}"); //做圖片,這些函式具體可以檢視PHP手冊 $destResource = imagecreatetruecolor($bgWidth, $bgHeight); imagealphablending($destResource, false); imagesavealpha($destResource, false); $color = imagecolorallocatealpha($destResource, 255, 255, 255, 127); imagefill($destResource, 0, 0, $color); //對每一張小圖片進行處理,生成在大背景圖裡,並生成css檔案 foreach ($fileMap as $innerMap) { foreach ($innerMap as $itemList) { foreach ($itemList as $item) { list($imageWidth, $imageHeight, $offsetX, $offsetY, $imageType, $fileName, $filePathname) = $item; if ($imageType === IMAGETYPE_PNG) { $srcResource = imagecreatefrompng($filePathname); } else if ($imageType === IMAGETYPE_JPEG) { $srcResource = imagecreatefromjpeg($filePathname); } imagecopy($destResource, $srcResource, $offsetX, $offsetY, 0, 0, $imageWidth, $imageHeight); imagedestroy($srcResource); //寫入css $posX = $offsetX === 0 ? 0 : "-{$offsetX}px"; $posY = $offsetY === 0 ? 0 : "-{$offsetY}px"; fwrite($writeHandle, "\n[icon-$bgName=\"$fileName\"]{width:{$imageWidth}px;height:{$imageHeight}px;background-position:$posX $posY;}"); } } } //壓縮級別 7 imagepng($destResource, "$bgSavePath.png", 7); imagedestroy($destResource); fclose($writeHandle); $shortCssSavePath = substr($cssSavePath, strlen(ROOT)); } /** * 將圖片的資訊處理成我們想要的資料結構 * @param [type] $dir [description] * @return [type] [description] */ function generateIconCallback_GetFileMap($dir) { $map = $sort = array(); $iterator = new DirectoryIterator($dir); foreach($iterator as $file) { if(!$file->isFile()) continue; $filePathname = str_replace("\\", '/', $file->getRealPath()); //這些函式可以檢視PHP手冊 $imageInfo = getimagesize($filePathname); $imageWidth = $imageInfo[0]; $imageHeight = $imageInfo[1]; $imageType = $imageInfo[2]; if(!in_array($imageType, array(IMAGETYPE_JPEG, IMAGETYPE_PNG))) { $fileShortName = substr($filePathname, strlen(ROOT) - 1); echo "<p> $fileShortName 圖片被忽略: 因為圖片型別不是png|jpg.</p>"; continue; } //這是我們的圖片規格,行高分別有 16 32 64 128 256 99999 foreach(array(16, 32, 64, 128, 256, 99999) as $height) { if($imageHeight <= $height) { $mapKey = $height; break; } } if(!isset($map[$mapKey])) $map[$mapKey] = array(); $filePathInfo = pathinfo($filePathname); $map[$mapKey] []= array($imageWidth, $imageHeight, $imageType, $filePathInfo['filename'], $filePathname, md5_file($filePathname)); $sort[$mapKey] []= str_pad($imageHeight, 4, '0', STR_PAD_LEFT) . $filePathInfo['filename']; } foreach($map as $k => $v) array_multisort($map[$k], SORT_ASC, SORT_NUMERIC, $sort[$k]); ksort($map, SORT_NUMERIC); return $map; } /** * 判斷目錄是否可寫 * @param string $dir 目錄路徑 */ function ensure_writable_dir($dir) { if(!file_exists($dir)) { mkdir($dir, 0766, true); @chmod($dir, 0766); @chmod($dir, 0777); } else if(!is_writable($dir)) { @chmod($dir, 0766); @chmod($dir, 0777); if(!@is_writable($dir)) { throw new BusinessLogicException("目錄不可寫", $dir); } } } generateIcon(); ?> <!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="css/Pink.css"> <title></title> </head> <body> <div>我們直接引入所生成的css檔案,並測試一下是否成功</div> <br> <div>這裡在span標籤 新增屬性 icon-Pink ,值為About-40,正常顯示圖片</div> <span icon-Pink="About-40"></span> </body> </html> |
呼叫以上程式碼,我們的瀏覽器是這樣顯示的:
然後css目錄生成了Pink.css檔案:
img目錄下生成了Pink.png檔案:
看看生成的背景圖是長啥樣子:
接下來我們再看一下所生成的圖片大小與Pink資料夾裡所有小圖片總和的大小,對它們做個比較:
從上圖可以看出,我們生成的圖片的大小明顯小於資料夾所有圖片的大小,所以在將100個小圖示下載下來的速度 會明顯小於 將背景圖下載下來和將CSS下載下來的速度。
當訪問量大時,或者小圖片的量大時,會起到很明顯的優化效果!!!
程式碼中的每一個點都基本上有註釋,很方便大家去理解,只要大家用心去看,肯定能將這一網站優化技術用到自己的專案中。
本次博文就寫到這!!!
如果此博文中有哪裡講得讓人難以理解,歡迎留言交流,若有講解錯的地方歡迎指出。
如果您覺得您能在此博文學到了新知識,請為我頂一個,如文章中有解釋錯的地方,歡迎指出。
互相學習,共同進步!