2012年5月9日 星期三

jQuery 練習題 : 圖片即時縮圖並裁剪顯示為正方形


標題看起來很繞口而且也有些詞不達意,所以就簡單的用幾張圖以及說明文字來讓大家了解,

一般做網站都會有要製作圖文上稿的功能,有時候一篇文章還需要指定一張封面圖的需求,

但有時候某些版面的編排製作上,圖片只能夠顯示正方形的圖,如果使用者在後台上了一張長方形的圖,

image

而前台只能顯示正方形,所以就會變成……

image

在後台裡,我們是可以在使用者上傳圖片後對圖檔做檢查,只要不是符合正方形的圖片就擋回去,

但有時候我們工程師這樣的作法卻反而會引起很多使用者的反彈,因為不能較使用者都要會圖片裁剪的能力以及工具,

所以跟使用者拉鋸的情況下,使用者多半都會接納圖片變形的結果,而讓網頁看起來就是充滿變形的圖片。

其實這種情況也是有多種解決的方法,

例如說可以用之前介紹的圖片裁剪功能,讓使用者上傳圖片後,再去裁剪要顯示的範圍,不過就要多花功夫去做這部份的功能,

或者是說,使用者上傳圖片之後,讓程式自動去裁剪要顯示的範圍並另存成新圖檔,這也是個不錯的方式,這以後會講到。

不過比較簡單的方式還是透過前端 Javascript 程式的操作,讓前端的圖片自動顯示一個正方形範圍而且還有縮圖的效果,

接下來我們就來練習做這個功能。


其實做這個功能並不是很困難,只要對 CSS 以及 jQuery 有一點點的基礎(例如…我)就可以做出這個功能,

這個功能的關鍵就在於「遮罩」…… 簡單講就是用一個 div 放在 img 的外面,然後設定好 div 的寬與高,然後再對 img 做縮圖處理,就是直接修改 img 的 width 與 height 值,圖片要顯示的範圍會因為圖片是直的還是橫的而有所不同,最後再去算出圖片要顯示的範圍,完成。

這個想法其實是用 imgAreaSelect 所裁剪功能時想到的,

http://odyniec.net/projects/imgareaselect/

image

看網頁原始碼就知道縮圖區塊的作法

image

所以我們就可以依照這樣的方式來做這個功能了。

 

CSS

一開始先設定好要使用的樣式內容,

<style type='text/css'>
    .container     { position: relative; overflow: hidden; }
    .container img { position: absolute; }
</style>

 

HTML

分別擺上三種不同的圖片,第一張是橫的,第二張是直的,第三張是正方形的,

這三張圖片的 class 都使用 crop,用來作為標示,方便接下來 Javascript 程式中的操作,

<img class="crop" src="http://dl.dropbox.com/u/26764200/Photos/1680x1050_Widescreen_Wallpaper_Summer_1920.jpg"/>
<br/>
<img class="crop" src="http://dl.dropbox.com/u/26764200/Photos/eiffel-tower-picture.jpg">
<br/>
<img class="crop" src="http://dl.dropbox.com/u/26764200/Photos/main_picture_large.jpg">
<br/>

 

Javascript

jQuery 使用的是最新版的 1.7.2

第一個方法式先找出 class 為「crop」的 img element,然後在 img element 外面包上一個 div 並且指定其 class 為「container」,

接下來再去設定剛剛動態增加的 div 的寬與高,這是用來顯示圖片的外框,

最後再逐一的對有包裹 div 的 img element 進行下一步驟的操作,

function CropImageToSquare(squareSize)
{
    $('img.crop').each(function(i, item)
    {
        $(item).wrap('<div class="container" />');
    });
    $('div.container').each(function(i, item)
    {
        $(item).css({'height': squareSize + 'px', 'width': squareSize+ 'px' });
    });
    $('.container img').each(function(i, item)
    {
        executeToSquare(item, 200);
    });        
}

 

在第二個步驟中,這個 function 就是要逐一處理每張圖片的顯示大小雨顯示區域範圍,

先取得原本圖片的寬與高,然後依據圖片是橫、是直、還是正方形的,判斷後取得顯示範圍的左上位置點的方位值,

最後就是修改圖片的顯示寬與高以及顯示的區域,這樣就完成了。

function executeToSquare(imageItem, squareSize)
{
    var width = $(imageItem).width();
    var height = $(imageItem).height();
    
    var tmp_width = 0;
    var tmp_height = 0;
    
    var position_top = 0;
    var position_left = 0;
    
    if(width == height)
    {
        tmp_width = squareSize;
        tmp_height = squareSize;
        
        position_top = parseInt(0 - ((tmp_height - squareSize )/2), 10);
        position_left = parseInt(0 - ((tmp_width - squareSize )/2), 10);
    }
    else if(width > height)
    {
        tmp_width = parseInt(((width / height) * squareSize ), 10);
        tmp_height = squareSize;
                    
        position_top = parseInt(((tmp_height - squareSize )/2), 10);
        position_left = parseInt(0- ((tmp_width - squareSize )/2), 10);
    }
    else if(height > width)
    {
        tmp_width = squareSize;
        tmp_height = parseInt(((height / width) * squareSize), 10);
        
        position_top = parseInt(0 - ((tmp_height  - squareSize )/2), 10);
        position_left = parseInt(((tmp_width - squareSize )/2), 10);        
    }
    
    $(imageItem)
    .attr('width', tmp_width ).attr('height', tmp_height )
    .css({ left: position_left, top: position_top });
}

 

看看 JSFIDDLE 吧

 

在 IE 7+, Firefox, Opera 上面都可以正常執行,

在縮圖後並且顯示圖片的中間位置,橫的、直的圖片都是顯示中間的區塊,而原本就是正方形的圖片就只是做縮放的處理,

image

 

但是 webkit 的 Chrome 與 Safari 就不聽指揮了…

image

這是因為圖片還沒有載入完全就開始進行縮圖與顯示位置的處理,一開始無法取得圖片正確的寬與高,

所以在 Chrome 與 Safari 就會變成是整張圖縮成正方形的變形狀況,這就不是我們要的結果,所以要來修改一下 Javascript 的部份。

 

修改

因為圖片還在載入而無法抓到圖片正確的寬度與高度,所以我們就要確保圖片載入完畢後才去執行我們的程式,

有關圖片預先載入的 jQuery Plugin 有很多種,而我所採用的是「jQuery Image Preload Plugin: Smart Preloader

http://www.egrappler.com/jquery-image-preload-plugin-smart-preloader/

會用它的原因是,我可以在載入每一張圖片時做處理,另外在全部圖片都載入完成後也可以做其他的處理,

而我要的就是確保在全部都載入完成後再去執行我們的程式,所以就是用這個套件啦。

 

修改過的 HTML 內容:

圖片多加上 id 屬性,另外圖片的來源位置都不放值。

<img class="crop" id="CropImage1" src=""/>
<br/>
<img class="crop" id="CropImage2" src="">
<br/>
<img class="crop" id="CropImage3" src="">
<br/>
<script type='text/javascript' src='http://dl.dropbox.com/u/26764200/javascript/smartpreload.js'></script>​

 

一開始的 Javascript 程式就修改為以下的內容:

先準備好要載入的圖片,載入每一張圖片時依照一開始的陣列內容的順序逐一放入正確的 img element 中,

當全部圖片載入完成後就是執行我們原本的 function。

$(document).ready(function()
{
    var cropImageSources = 
    [
        'http://dl.dropbox.com/u/26764200/Photos/1680x1050_Widescreen_Wallpaper_Summer_1920.jpg', 
        'http://dl.dropbox.com/u/26764200/Photos/eiffel-tower-picture.jpg', 
        'http://dl.dropbox.com/u/26764200/Photos/main_picture_large.jpg'
    ];
    $(document).smartpreload(
    { 
        images: cropImageSources, 
        oneachimageload: function(src) 
        {
            $.each(cropImageSources, function(index, value) 
            { 
                if(value == src) $('#CropImage' + (index+1)).attr('src', src);
            });
        }, 
        onloadall: function() 
        {
            CropImageToSquare(200);
        }
    });
    
});

 

修改後的程式在 Chrome / Safari 的顯示:

Chrome

SNAGHTMLc19dc8

Safari

SNAGHTMLc2d6a3

兩個瀏覽器都可以正確的執行並顯示正確的結果。

 

看看 JSFIDDLE 吧

 

 


當然不是這樣就結束,既然是練習,就讓我練習把這個功能給做成一個 jQuery Plugin 吧,這樣以後也可以方便重複使用,

這個 plugin 的功能提供兩種形式:

  1. 可以一次處理網頁上所有指定的圖片
  2. 可以單一處理網頁上指定的圖片

這一個 plugin 並沒有做得很彈性,唯一可以彈性處理的就是可以指定 Square 的 size,

而處理的圖片,img element 的 class 必須要設定使用「crop」,其他應該就沒有什麼要特別注意的,

 

先看看 jQuery plug – CropImageToSquare 的程式內容:

程式寫得很簡陋,請多見諒,有錯的請告訴我,謝謝。

/*!
 * CropImageToSquare - jQuery Plugin
 * version: 0.0.1 (2012/05/09)
 *
 * License: kevintsengtw.blogspot.com
 *
 * Copyright 2012 Kevin Tseng (tw)
 *
 */
 
(function($)
{  
    if (typeof (jQuery) === 'undefined') { alert('jQuery Library NotFound.'); return; }
 
    var CropImageToSquare = window.CropImageToSquare = {};    
 
    jQuery.extend(CropImageToSquare,{
 
        cropAll: function(options)
        {
            $('img.crop').each(function(i, item)
            {
                $(item).wrap('<div class="container" />');
            });
 
            $('div.container').each(function(i, item)
            {
                $(item).css({'height': options.squareSize + 'px', 'width': options.squareSize + 'px' });
            });
 
            $('.container img').each(function(i, item)
            {
                CropImageToSquare.executeToSquare(item, options.squareSize);
            });        
        },
        executeToSquare: function (imageItem, squareSize)
        {
            var width = $(imageItem).width();
            var height = $(imageItem).height();
            
            var tmp_width = 0;
            var tmp_height = 0;
            
            var position_top = 0;
            var position_left = 0;
            
            if(width == height)
            {
                tmp_width = squareSize;
                tmp_height = squareSize;
                
                position_top = parseInt(0 - ((tmp_height - squareSize )/2), 10);
                position_left = parseInt(0 - ((tmp_width - squareSize )/2), 10);
            }
            else if(width > height)
            {
                tmp_width = parseInt(((width / height) * squareSize ), 10);
                tmp_height = squareSize;
                            
                position_top = parseInt(((tmp_height - squareSize )/2), 10);
                position_left = parseInt(0- ((tmp_width - squareSize )/2), 10);
            }
            else if(height > width)
            {
                tmp_width = squareSize;
                tmp_height = parseInt(((height / width) * squareSize), 10);
                
                position_top = parseInt(0 - ((tmp_height  - squareSize )/2), 10);
                position_left = parseInt(((tmp_width - squareSize )/2), 10);        
            }
            
            $(imageItem)
            .attr('width', tmp_width ).attr('height', tmp_height )
            .css({ left: position_left, top: position_top })
            .show();
        }        
    });
 
    $.fn.cropImageToSquare = function(options)
    {
        $(this).wrap('<div class="container" />');
        $(this).parent().css({'height': options.squareSize + 'px', 'width': options.squareSize + 'px' });
        CropImageToSquare.executeToSquare(this, options.squareSize);
    };
 
})(jQuery);

 

第一種的使用情境:全部處理網頁上指定的圖片

HTML

<h1>jQuery Plugin - Crop Image To Square - Index 1</h1>
<hr/>
Square Size: <input type="text" id="TextSquareSize" value="200">px 
<input type="button" id="ButtonExecute" value="Crop Image to Square" />
<br><br>
 
<img class="crop" id="CropImage1" src=""/>
<a class="fancybox default" href="images/1680x1050_Widescreen_Wallpaper_Summer_1920.jpg" target="_blank">Picture1 - Original</a>
<br/><br/>
 
<img class="crop" id="CropImage2" src="">
<a class="fancybox default" href="images/eiffel-tower-picture.jpg" target="_blank">Picture2 - Original</a>
<br/><br/>
 
<img class="crop" id="CropImage3" src="">
<a class="fancybox default" href="images/main_picture_large.jpg" target="_blank">Picture3 - Original</a>
<br/><br/>

Javascript

<script type='text/javascript' src='js/jquery-1.7.2.min.js'></script>
<script type="text/javascript" src="js/fancybox/jquery.fancybox.pack.js?v=2.0.5" ></script>
<script type='text/javascript' src='js/CropImageToSquare.js'></script>
<script type='text/javascript' src='js/smartpreload.js'></script>
<script type='text/javascript'>//<![CDATA[ 
 
var counter = 0;
 
$(document).ready(function()
{
    $(".default").fancybox({
        autoSize: true,
        openEffect: 'elastic',
        closeEffect: 'elastic'
    });
 
    var cropImageSources = 
    [
        'images/1680x1050_Widescreen_Wallpaper_Summer_1920.jpg', 
        'images/eiffel-tower-picture.jpg', 
        'images/main_picture_large.jpg'
    ];
 
    $(document).smartpreload(
    { 
        images: cropImageSources, 
        oneachimageload: function(src) 
        {
            $.each(cropImageSources, function(index, value) 
            { 
                if(value == src) $('#CropImage' + (index+1)).attr('src', src);
            });
        }, 
        onloadall: function() 
        {
            var sizeValue = $.isNumeric($.trim($('#TextSquareSize').val())) ? parseInt($.trim($('#TextSquareSize').val()), 10) : 200;
 
            CropImageToSquare.cropAll({ squareSize : sizeValue });
        }
    });
 
    $('#ButtonExecute').click(function()
    { 
        var sizeValue = $.isNumeric($.trim($('#TextSquareSize').val())) ? parseInt($.trim($('#TextSquareSize').val()), 10) : 200;
 
        CropImageToSquare.cropAll({ squareSize : sizeValue });
    });        
});
 
//]]>  
 
</script>
  

執行結果

image

 

 

第二種的使用情境:單一處理網頁上指定的圖片

HTML 與前面的一樣,有稍為變化的就是 Javascript 的部分,

<script type='text/javascript'>//<![CDATA[ 
 
var counter = 0;
 
$(document).ready(function()
{
    $(".default").fancybox({
        autoSize: true,
        openEffect: 'elastic',
        closeEffect: 'elastic'
    });
 
    var cropImageSources = 
    [
        'images/1680x1050_Widescreen_Wallpaper_Summer_1920.jpg', 
        'images/eiffel-tower-picture.jpg', 
        'images/main_picture_large.jpg'
    ];
 
    $(document).smartpreload(
    { 
        images: cropImageSources, 
        oneachimageload: function(src) 
        {
            $.each(cropImageSources, function(index, value) 
            { 
                if(value == src) $('#CropImage' + (index+1)).attr('src', src);
            });
        }, 
        onloadall: function() 
        {
            $('#CropImage1').cropImageToSquare({ squareSize: 150 });
            $('#CropImage2').cropImageToSquare({ squareSize: 150 });               
        }
    });
 
});
 
//]]>  
 
</script>

 

執行結果:

第三張沒有處理是要做個突顯,與前面兩張有做處理的圖片做個比較。

image

 

最後來個無聲的操作影片示意一下:

 

另外就是這個 jQuery Plugin 的原始檔,下載位置如下:

http://dl.dropbox.com/u/26764200/Lab/20120509_jQuery_CropToSquare.zip

 


因為我只是想單純的做個練習,所以也沒有去找有無相同功能的 jQuery Plugin,

結果看到今天 (2012-05-09) jQuery4U 的文章「jQuery4U  - Recently Released jQuery Plugins」,

其中就有介紹到一個功能一樣卻強大好幾倍 jQuery Plugin ……

image

jQuery NailThumb

http://www.garralab.com/nailthumb.php

image

這個 plugin 有縮圖跟顯示範圍的功能外,除了可以指定顯示正方形以外,也可以顯示橫長方形或直長方形,

縮圖的部分,可以顯示裁剪區域,也可以顯示原圖的縮圖或是原圖一比一的裁剪範圍,

縮圖還可以指定動畫,另外比較強的就是可以指定你要顯示的區域,不是只有正中間的那一塊,可以指定九宮格的其中一個區塊,

這個 plugin 功能相當不錯,有興趣的朋友可以前往了解,

因為這個套件的應用相當地多,也只有你前往它的網站並瀏覽「Example」才能夠明白這個套件好用的地方在哪邊。

 

這一次久違的 jQuery 的練習題就到這邊為止……

 


更新:

文章發佈後馬上得到 Miau Hunag 的指點,於是就把程式修改如下,不需要用到 smart prload,

看看 JSFIDDEL 的修改與執行結果:

感謝 Miau Hunag


 

以上

8 則留言:

  1. 前陣子才寫過類似的功能,
    關於webkit的部份,應該不需要請出preload吧,
    把$(function(){....});
    換成$(window).load(function() { ........ });
    就可以了說 : ~

    回覆刪除
    回覆
    1. 感謝 Miau Huang 的指教,依據你的方式修改後就可以在 WebKit 瀏覽器上正常執行了,
      謝謝 ^_^

      刪除
  2. hi~ 我有問題想請教
    如果我加多幾張圖片, 單單在:
    var cropImageSources =
    [
    'images/1680x1050_Widescreen_Wallpaper_Summer_1920.jpg',
    'images/eiffel-tower-picture.jpg',
    'images/main_picture_large.jpg'
    ];

    加上圖片,
    還有在:
    $('#CropImage2').cropImageToSquare({ squareSize: 100 });

    這裡加, 再引用 CropImage2/3/4/5.... 就可以了吧?

    我把div放在table裡面了, 剛開始可以,但之後就顯示不出圖片了
    請問這是為什麼呢?

    回覆刪除
    回覆
    1. 你好,針對你所提供的訊息內容,我不清楚你的 table 與 div , image 的架構為何
      僅知道你應該是把圖片放片 table 的 cell(td) 中,然後應該在 td 中有放 div,最後再把 image 放到 div 裡面,
      我以這樣的條件來做測試,測試結果是一切正常的,
      你可以稍微看一下我所寫的 plugin 內容的話,其實我是在原本的 image 外面動態使用 div 來做 warp,
      最後,建議你可以使用 firefox + firebug 或是 chrome 來觀察 DOM 的變化。

      刪除
  3. 您好, 有沒有辦法弄成顯示長方形的按比例置中縮圖?? 我網上找了很久, 只有你這有教學~.~但是正方形不太適合...希望能幫個忙~感謝您~

    回覆刪除
    回覆
    1. 長方形呀......
      我不是很確定你的需求,是將原本就是長方形的圖片作等比縮圖呢?
      還是說要把任意尺寸的圖片都一律修改為長方形的致中縮圖呢?
      不過我相信應該都是可以的,只要抓出原始圖片的長寬並且決定好你要縮圖的長寬比,
      我想應該是可以的,我這篇文章的 javascript 程式相當簡單,應該可以做出修改。

      刪除
    2. 另外我在這篇文章有介紹的[jQuery NailThumb]應該就可以滿足你的需求。

      刪除
  4. 嗯~我是要將任意圖片變成長方形的~

    那個[jQuery NailThumb]我在網上也有看過...但是教學沒你這那麼詳細-_-我搞了半天都搞不成...

    我是要用在購物車程式裡的~@@商品圖片很多不同大小的圖片

    回覆刪除

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。