2016/02/15

Javascript 等高響應佈局

今天要跟大家分享 “等高響應佈局” 的實作流程。
在討論之前,必須先認識等高響應佈局是種怎樣的佈局:


等高響應式局不同於瀑布流,瀑布流是指 ”內容寬度相同,高度不同且依高度排列排列下一行的項目”。

而等高響應佈局則是 ”每一行的內容同高、每一行切齊頭尾且內容與內容之間的寬度固定”,
可以參考 PEXELS 的呈現方式

等高響應佈局通常用於圖片呈現,訴求能讓各種不同比例、大小的圖片,簡單乾淨的呈現在佈局上,在動手寫程式之前,先把需求整理條列如下:
  1. 每一行的高度相當
  2. 自動分配圖片屬於哪一行
  3. 自動拉伸項目切齊頭尾
範例使用的 html 相當簡單如下:

<div class="foo">
    <ul>
        <li><a href="#"><img src="#"></a></li>
        <li><a href="#"><img src="#"></a></li>
        ...
    </ul>
<div>


為了避免空白與斷行字元的干擾,因此 a、img 的 display 都設為 block,並且讓 li 置左對齊 float: left,樣式設定如下。

.foo {
    margin: 40px;
    background-color: #ddd;
}

    ul {
        padding: 0; /*清除預設樣式*/
        list-style: none;
    }

        ul::after { /*清除浮動*/
            content: '\200B'; /*空字元*/
            display: block;
            height: 0;
            visibility: hidden;
            clear: both;
        }

        li {
            float: left;
        }

            a {
            <data></data>isplay: block;
            }

                img {
                    display: block;
                    width: auto;
                    height: auto;
                }

首先,必須先定義一個基礎高度,匹配給所有圖片,辨別該圖片的歸屬行數:



第三步,計算該行所有圖片相加的總寬度,並求出總寬度與佈局寬度的比例:



第四步,依序放大圖片:



知道概念之後,我們開始寫程式吧!
在範例中,必須等到圖片加載完成,js 才能抓圖片高度:

window.load = function(){
    ...
}

先宣告想要的高度:
var _height = 200;

接著要抓取 .foo 節點,算出他的寬度。當然也要抓取所有的 img:
var $env = document.querySelector('.foo'), //抓 .foo
    $env_w = $env.getBoundingClientRect().width, //取得 .foo 的寬
    $imgs = $env.querySelectorAll('img'); //抓所有的 img

此時建立一個叫 Item 的類別,它的項目實例會記錄 img 的節點與寬度資訊。
我們可以預先把計算過的資訊放進去:
var Item = function(imgNode){
        this.node = imgNode;
        this.nodeInfo = this.node.getBoundingClientRect();
        this.width = this.nodeInfo.width * ( _height / this.nodeInfo.height ); //存放預先計算過的寬度資訊
        this.height = this.nodeInfo.height;
    }

當然,我們也需要一個方法調整項目中的寬/高比例,一個方法設定真正的 node 寬/高,最好是寫在類別原型中:
Item.prototype.reSize = function(prop){ //調整實例寬高的方法,參數是一個比例
    this.width *= prop;
    this.height *= prop;
}

Item.prototype.setSize = function(){ //設定圖片寬高的方法
    this.node.style.width = this.width + 'px';
    this.node.style.height = this.height + 'px';
}

有了方法之後,我們可以開始讓所有 img 生產項目實例了,別忘了用一個陣列把它們通通存起來:
var $items = [];

for( var i = 0; i < $imgs.length; i++ ){
    var $obj = new Item($imgs[i]);

    $items.push($obj);
}

我們得到了一個塞滿項目的陣列 $items,接下來該來分配行數歸屬了。
規則是圖片寬度一一相加,一但大於 .foo 的寬度時,我們會做一些事情,最後讓最後一個相加,且超出 .foo 寬度的項目等於下一行的第一個 item。
var _lineWidth = 0, //存放這一行圖片寬度相加的值
    _firstItem = 0; //第一個項目

for( var i = 0; i < $items.length; i++ ){

    var $obj = $items[i],
        $obj_w = $obj.width;

    _lineWidth += $obj_w;

    if( _lineWidth > $env_w ) { //檢測寬度是否在一行內,若大於一行寬度,則安排至下一行


        //做一些事情...


        _firstItem = i;

        _lineWidth = $obj_w;
    }
}

一但 _lineWidth 大於 .foo 的寬度,減去最後一個項目,就可以得知這一行圖片目前加總的寬度,也就可以用 ( .foo 寬度 / 這一行的寬度 ) 計算出這一行應該被放大的比例,再把這個比例一一存入實例。
var _lineWidth = 0, //存放這一行圖片寬度相加的值
    _firstItem = 0; //第一個項目

for( var i = 0; i < $items.length; i++ ){

    var $obj = $items[i],
        $obj_w = $obj.width;

    _lineWidth += $obj_w;

    if( _lineWidth > $env_w ) { //檢測寬度是否在一行內,若大於一行寬度,則安排至下一行

        var _prop = $env_w / ( _lineWidth - $obj_w ); //這一行應該被放大的比例

        for( var j = _firstItem; j < i; j++ ) { //這一行的項目一一計算比例並存入實例
            var $obj = $items[j];

            $obj.reSize(_prop);
        }

        _firstItem = i;

        _lineWidth = $obj_w;
    }
}

最後,所有項目都得知自己的寬度,再一一調整 node 比例就好:
for( var i = 0; i < $items.length; i++ ){ //分配行
    $items[i].setSize();
}

到此完成整個效果,當然你可以用各種方法優或性能,或讓它支援更多情境(例如 RWD)。
我在 github 有一個關於 等高響應佈局 的庫,你也可以協助我改進它,感謝大家囉!

沒有留言:

張貼留言