網頁

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]我在網上也有看過...但是教學沒你這那麼詳細-_-我搞了半天都搞不成...

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

    回覆刪除