1. 程式人生 > >【js 實踐】js 實現木桶布局

【js 實踐】js 實現木桶布局

cto enter 最後一行 scrip fine inner get code 兩個

還有兩個月左右就要準備實習了,所以特意練一練,今天終於搞定了js 的木桶布局了

這一個是按照一個插件的規格去寫的以防以後工作需要,詳細的解釋在前端網這裏 http://www.qdfuns.com/notes/37573/535e6e8bf4a6ab06823943628936de87.html

這裏只出示一下代碼了啦,如果有什麽不足的地方請指出無盡感激:

html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>demo2</title>
</head>
<body>
    <div class
="container"> <div class="gallary-item-inner"><img src="images/1.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/2.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/3.jpg" alt=""></div> <div class
="gallary-item-inner"><img src="images/4.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/5.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/6.jpg" alt=""></div> <div class="gallary-item-inner
"><img src="images/7.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/8.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/9.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/10.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/11.jpg" alt=""></div> <div class="gallary-item-inner"><img src="images/12.jpg" alt=""></div> </div> <div class="add">+12</div> <link rel="stylesheet" type="text/css" href="css/normal.css"> <link rel="stylesheet" type="text/css" href="css/demo2.css"> <script type="text/javascript" src="js/demo2.js"></script> <script type="text/javascript"> var container = document.querySelectorAll(.container)[0], items = document.querySelectorAll(.gallary-item-inner img), addBtn = document.querySelectorAll(.add)[0], barrel = null; window.addEventListener(load, function() { barrel = new BarrelLayout(container, items, 150); }); addBtn.addEventListener(click, function() { var frag = document.createDocumentFragment(); for (var i = 0; i < 20; i++) { var num = Math.floor(Math.random() * 12 + 1), path = images/ + num + .jpg; var div = document.createElement(div), img = document.createElement(img); img.src = path; div.appendChild(img); div.className = gallary-item-inner; frag.appendChild(div); } container.appendChild(frag); barrel.refresh(document.querySelectorAll(.gallary-item-inner img)); }); </script> </body> </html>

css:

.gallary .gallary-item{
    font-size: 0;
}
.gallary .gallary-item .gallary-item-inner{
    display: inline-block;
    font-size: 0;
}
.barrel-container .barrel-row{
    line-height: 0;
    text-align: left;
}
.add{
    position: fixed;
    bottom: 80px;
    right: 50px;
    width: 80px;
    height: 80px;
    line-height: 80px;
    border-radius: 50%;
    box-shadow: 0 0 20px rgba(0,0,0,0.3);
    text-align: center;
    cursor: pointer;
}

js

/*
    木桶布局類
    參數:
       wrapper: 
       類型: HTMLElement
       描述: 木桶布局的初始化包裹層由用戶自己定義

       items:
       類型: HTMLElementList
       描述: 需要木桶布局的子項

          baseHeight:
       類型: Integer
       描述: 用戶規定木桶布局每一行的基礎高度 實際輸出時會與這個高度有差異

          流程:
                  計算 ==> 整理 ==> 渲染dom


          屬性:
            barrelWrap:
            類型: HTMLElement
            描述: 保存用戶傳進來的外包裹層

            items:
            類型: HTMLElementList
            描述: 保存用戶傳進來的所有需要木桶布局的子元素集
            
            baseHeight:
            類型: Integer
            描述: 接收用戶設置的基本高度
            
            lastRow:
            類型: Array
            描述: 保存上一次渲染木桶布局後最後一行的元素

            lastItemIndex:
            類型: Integer

        方法:
            calc:
            參數: 
                itemList: (Array) 接收需要木桶布局的dom 元素列
            返回值: Object
                    {
                        elemArr: 二維數組,保存每一行應有的元素
                        rowHeightArr: 一維數組,保存每一行的實際寬度
                    }
            類型: HTMLElementList
            描述: 將需要進行木桶布局的 dom 元素(通常是img) 傳入這個函數中
                  首先會根據元素的寬高比例計算出每一個元素按比例縮放的寬度
                  根據寬度計算出一行能夠放入多少個元素
                  當計算完一行應有元素時再計算出該行的高度, 公式為:
                  該行所有元素寬度總和 / 用戶定義的基本高 = 該行在瀏覽器顯示的寬度 / y
                  y 為最後的運算結果
                  最後,將每一個木桶布局元素的高度設置為 y 其寬度總和便會自動填充滿整行

            render: 
            參數: rowsArr: (Array) 每一行需要渲染的dom 元素
                  rowHeightArr: (Array) 每一行的實際行高
            返回值: void
            描述: 對calc 方法返回的數據進行渲染dom
                  此方法會首先判斷有沒有 class="barrel-container" 這個元素存在
                  如果不存在證明是第一次初始化 
                  為用戶指定的 wrapper 元素下生成一個ul類名為barrel-container
                  在container 下面輸出dom

                  如果barrel-container 存在證明是重新渲染
                  那麽將最後一個li 移除再緊接著輸出

            init:
            參數: null
            返回值: void
            描述: 調用上面兩個方法渲染dom

            refresh:
            參數: newItemsList: (HTMLElemsList)
            描述: 為了減少dom 的渲染
                  當從服務器將加載圖片添加到包裹層中時可調用此方法
                  此方法會根據上一次渲染後的 lastItemIndex 對新元素數組進行切割
                  切割完成後和上一次渲染後的最後一行元素列合並組成新的渲染數組
                  之後依次調用 calc() render() 完成輸出
                  *註意一點:
                   上一次渲染後最後一行的元素已經設置好了高度所以在計算前要將
                   最後一行的元素清空樣式防止布局錯亂
                    

*/

function BarrelLayout(wrapper, items, baseHeight) {
    this.barrelWrap = wrapper;
    this.items = items;
    this.baseHeight = baseHeight;
    // 下面是函數附帶的屬性
    this.lastRow = [];            // 保存上一次加載元素中最後一行的元素
    this.lastItemIndex = 0;        // 保存最後一個元素的下標
    this.init();
}
BarrelLayout.prototype.init = function(){
    var layoutData = this.calc(this.items);
    // 保存最後一行的元素
    this.lastRow = layoutData.elemArr[layoutData.elemArr.length - 1];
    // 保存最後一個元素的index
    this.lastItemIndex = this.items.length;
    this.render(layoutData.elemArr, layoutData.rowHeightArr);
};

BarrelLayout.prototype.calc = function(itemsList){
    // 私有變量
    var resultElemArr = [],            // 最終返回的保存每一行的數組
        resultRowHeightArr = [],    // 最終返回的保存每一行的基本行高的數組
        tempElemArr = [],            // 保存每一行應有元素的數組
        widthRate = 0,                // 元素的寬度比例
        heightRate = 0,                // 元素的高度比例
        totalWidth = 0;                // 行元素的寬度總和
        

    var len  = itemsList.length;

    for (var i = 0; i < len; i++) {
        // 計算元素寬高比例
        // 再求出縮放下的寬度
        widthRate = itemsList[i].offsetWidth / itemsList[i].offsetHeight;
        var curElemWidth = this.baseHeight * widthRate;
        totalWidth += curElemWidth;

        // 如果當元素相加寬度小於容器寬度將它推進 tempElemArr 數組
        // totalWidth 加上這個元素的寬度
        if(totalWidth <= this.barrelWrap.offsetWidth) {
            tempElemArr.push(itemsList[i]);

            // 如果當前的元素是最後一個且總寬度沒有超過容器寬度
            // 將此時的tempElemArr 放入 this.rows 數組中
            
            if(i === len - 1) {
                resultElemArr.push(tempElemArr);
                // 行高設置為默認的baseHeight
                resultRowHeightArr.push(this.baseHeight);
            }
            

        }else {
            // 如果當前元素寬度相加大於容器寬度 進行如下操作
            // 1.計算當前元素寬度總和與baseHeight 的比率 根據比率設置當前行的高度
            // 從而設置行內的每一個元素的高度 調整到最適合的寬度
            heightRate = this.baseHeight / (totalWidth - curElemWidth);
            // 精確高度到兩位小數
            var curColHeight = Math.floor(((this.barrelWrap.offsetWidth * heightRate) * 10)) / 10;
            // 2.將這一行的行高推入 rowHeight 數組
            resultRowHeightArr.push(curColHeight);
            // 3.將這一行應有的元素推入
            resultElemArr.push(tempElemArr);
            // 4.tempElemArr 數組重新填入這個超出容器寬度的元素
            tempElemArr = [itemsList[i]];
            // 5.重設totalWidth 為這個元素的寬度
            totalWidth = curElemWidth
            
            if(i === len - 1) {
                resultElemArr.push(tempElemArr);
                // 行高設置為默認的baseHeight
                resultRowHeightArr.push(this.baseHeight);
            }
            
        }
    }

    return {
        elemArr: resultElemArr,
        rowHeightArr: resultRowHeightArr
    }
};


BarrelLayout.prototype.render = function(rowsArr, rowHeightArr){
    var container = document.querySelectorAll(‘.barrel-container‘)[0];
    if(container === undefined) {
        container = document.createElement(‘ul‘);
        container.className = ‘barrel-container‘;
    }else {
        // 如果barrel-container 存在證明是刷新操作
        // 此時要將視圖中容器裏面最後一行的li 刪掉
        // 然後再生成元素 加入到容器中
        var rows = container.querySelectorAll(‘.barrel-row‘);
        container.removeChild(rows[rows.length - 1]);
    }
    
     for (var i = 0; i < rowsArr.length; i++) {
         var li = document.createElement(‘li‘);
         li.className = ‘barrel-row‘;
         for (var k = 0; k < rowsArr[i].length; k++) {
             rowsArr[i][k].style.height = rowHeightArr[i] + ‘px‘;
             rowsArr[i][k].parentNode.style.display = ‘inline-block‘;
             
             li.appendChild(rowsArr[i][k].parentNode);
             container.appendChild(li);
         }
     }

     this.barrelWrap.appendChild(container);
};

BarrelLayout.prototype.refresh = function(newItemsList){
    // 1. 首先調整最後一行的元素排列
    // 對新的元素列表進行切割 分離出新加入的元素 根據this.lastItemIndex進行切割
    var newList = Array.prototype.slice.call(newItemsList, this.lastItemIndex),
        lastRow = this.lastRow; 

    for (var i = 0; i < lastRow.length; i++) {
        lastRow[i].style = ‘‘;
    }
    // 將新加入的元素與上一次渲染後最後一個行的元素列連接起來
    var totalList = lastRow.concat(newList);
    var layoutData = this.calc(totalList);
    this.render(layoutData.elemArr, layoutData.rowHeightArr);
    // 對 this.lastItemIndex 重新賦值為下一次刷新做準備
    this.lastItemIndex += totalList.length - lastRow.length;
    this.lastRow = layoutData.elemArr[layoutData.elemArr.length - 1];

    console.log(lastItemIndex);
    console.log(lastRow);
};

【js 實踐】js 實現木桶布局