Ajax + 懶加載 + 預加載 實現瀑布流

Ajax + 懶加載 + 預加載 實現的瀑布流

首先, 效果如下
在這裏插入圖片描述

同時在掛一個github鏈接,裏面有一些自己寫的demo, 也有很多素材,可以選擇性打開選擇(如果不需要的話可以忽略這條)我的github該項目地址

話不多說, 既然要寫出這個簡單的小demo, 同時掌握ajax + 懶加載 + 預加載三樣雖然簡單但卻特別常用的技術, 我們還得追根溯源拆分成三大模塊說起(如果對這三塊知識瞭解比較好, 有時間可以看看下面的內容, 也給出朋友你的一份建議, 如果沒有時間可以直接跳到最下方看demo操作》)

懶加載和預加載

懶加載

先來說說懶加載,我們來看看官方定義
在這裏插入圖片描述
一般來說官方的話說的都不是給新手看的,我個人的理解懶加載就是惰性加載, 比如淘寶首頁有幾百上千張圖片, 當我們打開淘寶頁面的時候我們要一次性把這幾百幾千張圖片都加載過來嗎, 當然不用, 因爲有的用戶根本就不會滑下去看那下面的那麼多圖片, 大多數人打開淘寶就是搜索, 但是我們如果一次性加載完畢的話首頁是不是很難打開, 於是乎前輩們想到了先加載首屏, 也就是打開淘寶不滑動鼠標滾輪的前提下大家能看到的第一版內容, 而剩下的內容當用戶有需求並且華東鼠標滑到那一塊之前一點點距離再進行加載, 稱之爲懶加載

說到底懶加載就是優化頁面的一種方案, 只要我們按需加載那麼首屏加載一定會很快, 不會造成過多的網絡請求的負擔

舉個例子如下, 我在body中設置有兩個盒子, 一個爲首屏盒子是藍色, 一個爲box盒子爲白色, box盒子中有一個loading-box是我們將要用來做懶加載的盒子, 我用一個顏色的變化來表示盒子中的圖片的加載

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>懶加載</title>
    <style>
        body {
            margin: 0;
            background-color: #eeeeee;
        }
        .first-screen-content {
            width: 1190px;
            height: calc(100vh - 40px);
            margin: 40px auto 0;
            background-color: rgba(30, 144, 255, 0.5);
            color: #fff;
            font-size: 48px;
            text-align: center;
            line-height: 800px;
        }

        .box {
            position: relative;
            width: 1190px;
            margin: 80px auto 40px;
            height: 800px;
            background-color: #fff;
            font-size: 48px;
            text-align: center;
            line-height: 800px;
        }
        .loading-box {
            width: 100px;
            height: 100px;
            background-color: rgba(255, 215, 0, 0.1);
            position: absolute;
            bottom: 0;
            left: 50%;
            transform: translate(-50%);
            transition: opacity 2s linear;
            font-size: 10px;
            color: #000;
            line-height: 24px;
        }
    </style>
</head>
<body>
    <div class="first-screen-content">我是首屏內容</div>
    <div class="box">
        我這裏面有很多的圖片和文字
        <div class="loading-box"></div>
    </div>
</body>
</html>

目前效果如下

在這裏插入圖片描述
透明金色的小方塊在我滑下去的時候依然是透明的, 我們可以理解爲這個金色小方塊並沒有加載出來, 或者可以理解爲根本沒有這個元素, 我因爲顯示直觀一點, 給了透明度, 假設這個小方塊有1個G那麼大(說的比較誇張哈哈), 那麼這個金色小方塊沒有加載出來的話, 那我們加載頁面的速度就會快上不少, 因爲我們至少不用去加載這一個G的東西, 爲什麼要這麼做呢, 因爲大部分用戶打開網頁甚至不會滾動往下看, 在電商類網頁中大多都是這樣, 用戶都不會下去看我還加載他, 不是吃飽了撐的 用戶也覺得難受, 打開個網頁這麼慢, 90%的時間都用在加載這個小方塊的身上了, 現在好了我們不用加載, 當用戶滑下去想看這一塊我們再加載這一塊也不遲

當用戶滑到接近那一塊的時候, 我們需要進行加載那個金色小方塊, 將他從0.3透明度變換成1透明度表示裏面的數據加載出來, JS代碼如下

(function(){
            const loadingBox = document.querySelector('.loading-box'); 
            // 入口函數
            const init = () => {
                bindEvent();
            }

            // 綁定事件
            const bindEvent = () => {
            	// 監聽鼠標滾輪事件
                window.onscroll = () => {
                    // 獲取到滾輪滾動的距離
                    let scrollTop = document.documentElement.scrollTop;
                    // 獲取到窗口的高度
                    let clientHeight = document.documentElement.clientHeight;

                    console.log(scrollTop, clientHeight);

                    // 當我們scrollTop > clientHeight的時候我們就開始加載金色小方塊
                    // demo中這樣, 在項目中肯定是需要更加精確的計算
                    (scrollTop > clientHeight) && loadingImage();      
                }
                
                loadingBox.addEventListener('transitionend', () => {
                    console.log(this);
                    loadingBox.innerHTML = '<span>經過懶加載, 我這個1G的東西加載出來啦</span>'
                }, false)
            }

            // 加載圖片函數
            const loadingImage = () => {
                console.log(loadingBox)
                loadingBox.style.opacity = '1';
                
            }

            init();

        })()

此時效果如下,我們也就完成了懶加載
在這裏插入圖片描述

懶加載又叫按需加載,記住啦

預加載

我們再來看看預加載的官方定義

在這裏插入圖片描述
我個人對於預加載的理解爲, 當一張圖片在加載過程中, 如果網速過慢, 他會出現一部分一部分的加載並出現在頁面中, 這樣對用戶體驗並不是很好,會顯得頁面很卡頓, 於是前輩們推出預加載機制, 也就是在圖片未完全加載完成之前我們並不將他展示進頁面, 等到他完全加載完畢以後我們再將其放進頁面元素中

我們先來看看沒有進行預加載的頁面和進行預加載的頁面分別是什麼場景, 這將幫助你更好的理解預加載的作用

  • 沒有進行預加載的圖片,即使我網速足夠快, 亞馬遜在進入鑽石系列電動牙刷的時候主要區域的橙色圖片也是一部分一部分加載出來, 如果遇到網速特別慢的時候會相當的膈應人啊

在這裏插入圖片描述

  • 進行預加載的案例我就自己寫了一個, 也是更好爲大家理解預加載的寫法和原理
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>懶加載</title>
    <style>
        body {
            margin: 0;
            background-color: #eeeeee;
        } 
        .box {
            position: relative;
            width: 1190px;
            margin: 80px auto 40px;
            height: 800px;
            background-color: #fff;
            font-size: 48px;
            text-align: center;
            line-height: 800px;

        }
        
    </style>
</head>
<body>
    
    <div class="box">
        <!-- 我們要在這個盒子裏面插入一張圖片 -->
    </div>

    <script>
        
        (function() {
            // 要進行預加載的圖片
            const readyImage = {
                src: 'http://img3.imgtn.bdimg.com/it/u=3870619358,3104020013&fm=26&gp=0.jpg',
                width: '500',
                height: '428' // 一般如果有預加載操作, 後臺會給寬高,
            }

            // 頁面入口函數
            const init = () => {
                createImage(readyImage);
            }

            // 預加載方法, 該方法接收三個參數
            // imageDOM: 加載完成的圖片元素
            // proxyDOM: 在圖片加載好之前的替代元素
            // parentDOM: 圖片元素要插入的父級
            const loadingReady = (imageDOM, proxyDOM, parentDOM) => {
                console.log(imageDOM);
                console.log(document.querySelector('.box'))
                parentDOM.appendChild(proxyDOM);
                imageDOM.onload = () => {
                    // 當圖片加載完成以後我們要把圖片元素替換掉之前的遮罩元素
                   setTimeout(() => {
                    parentDOM.replaceChild(imageDOM, proxyDOM); 
                   }, 1000)
                }
            }

            // 創建圖片
            const createImage = (src) => {
                const image = new Image(); //創建一個image元素
                image.src  = readyImage.src;
                const mask = document.createElement('div');
                mask.style.width = readyImage.width + 'px';
                mask.style.height = readyImage.height + 'px';
                mask.style.display = 'inline-block';
                mask.innerText = '正在加載中';
                mask.style.background = '#999';
                loadingReady(image, mask, document.querySelector('.box'));
            }


            init();

        })()
     
    </script>
</body>
</html>

實現效果如下, 剛發現上面的代碼都是用ES6箭頭函數和let&const寫的,let 和 const是新的聲明變量的方式, 箭頭函數是函數的簡寫但是與函數有一些區別, 可以百度一下箭頭函數和let&const, 太晚了就懶得改了

在這裏插入圖片描述

我們發現, 當圖片沒有加載完成的時候頁面始終顯示的是一塊灰色的底盤並且提示我們正在加載中, 這就是預加載。

Ajax

ajax作爲元老級別的網絡請求技術, 絕對值得新手探究和學習, ajax也是我們跟後臺進行交互的第一步, 這裏我默認大家已經懂了ajax的定義和一些基本常識, 我們直接從封裝講起

// ajax函數, 用來和後端進行交互
var ajax = (function() {
    // url - 地址, method - 請求方法(POST or GET), callback - 請求的回調函數, data - 約定好傳遞的數據, flag - 是否異步
    return function(url, method, callback, data, flag) {
        // xhr就是用來存儲請求與響應的信息
        var xhr = null;
        // 兼容性處理
        if(window.XMLHttpRequest) {
            xhr = new XMLHttpRequest();
        }else {
            xhr = new ActiveXObject('Microsoft.XMLHttp');
        }
        // 監聽此次數據請求的狀態, 最好寫在send方法之前, 原因可以自行百度, 註釋難寫,
        // 跟網速有關可能會監聽不到
        xhr.onreadystatechange = function() {
            // 只有readyState的值到了4了那麼這個響應就結果也就來了, 1 2 3都是在請求中
            if(xhr.readyState === 4) {
                // http狀態碼 200一般爲OK
                if(xhr.status === 200) {
                    // 執行回調函數並且把 返回的數據xhr.responseText傳進callback中
                    callback(xhr.responseText);
                }else {
                    console.log('error');
                }
            }
        }

        // 統一一下格式, 無論調用者傳進來的是get還是GET 都會被轉化爲GET
        method = method.toUpperCase();

        if(method === 'GET') {
            var date = new Date();
            var timer = date.getTime();
            // 建立連接
            xhr.open(method, url + '?' + data + '&timer=' + timer, flag);
            // 發出請求
            xhr.send();
        }else if(method === 'POST'){
            // 建立連接
            xhr.open(method, url, flag);
            // 設置請求頭
            // application/x-www-form-urlencoded 表示數據是以key=value的形式拼接的
            // 還有一些其他的Content-Type比如application/json 表示數據以json格式存在,
            // 更多可以瞭解一下網絡知識
            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
            // 發出請求
            xhr.send();
        }
    }
})()

上方封裝了一個比較簡單的ajax網絡請求, 註釋也已經打好, 如果對上面的函數有什麼疑問或者覺得需要改進的地方可以私信, 到這我們的ajax網絡請求就爲止了

當上面三個知識點透徹以後,我們就萬事俱備只欠東風了,下面我們來寫我們上面樣圖中的瀑布流, 直接貼代碼註釋都已經寫好

  • HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>瀑布流</title>
    <!-- 引入樣式 -->
    <link rel="stylesheet" href="./index.css">
</head>
<body>
    <!-- 父級wrapper -->
    <div class="wrapper">
        <!-- 標題區域 -->
        <div class="title">
            <h2>民間制柴窩點</h2>
        </div>
        <!-- 內容進行瀑布流的區域 -->
        <div class="content">
            <ul class="list">
                <!-- 分爲四列, 豎向, 對應頁面中的四列,並且四個li都等待元素進行填充 -->
                <li class="item">
                </li>
                <li class="item">
                </li>
                <li class="item">
                </li>
                <li class="item">
                </li>
            </ul>
        </div>
    </div>
    <!-- 引入之前封裝好的ajax -->
    <script src="./ajax.js"></script>
    <!-- 引入實現功能的index.js -->
    <script src="./index.js"></script>
</body>
</html>
  • CSS
/* 初始化, 這樣初始化有一丟丟的多餘操作, 在企業中一般公司會給一個reset.css */
* {
    margin: 0;
    padding: 0;
    list-style: none;
}

/* :root可以理解爲html,  */
:root, body {
    width: 100%;
    height: 100%;
    /* css3的漸變, 就可以理解爲有過渡效果的背景顏色 */
    background-image: linear-gradient(to right, #ccc, #fff);
}

/* 父級wrapper基礎樣式 */
.wrapper {
    margin: 20px auto;
    width: 1190px;
    background-color: #fff;
    
}

/* 給標題的容器樣式 */
.wrapper .title {
    background-image: linear-gradient(to right, #1e90ff, rebeccapurple);
    padding: 10px 0;
    /* 陰影, box-shadow: x偏移 y偏移 模糊距離 顏色; 當然還有一些屬性, 我這裏寫的是我用到屬性的含義 */
    box-shadow: 18px 16px 20px #999;
}

.wrapper .title h2 {
    /* margin: 20px 0; */
    text-align: center;
    font-size: 30px;
    color: rgb(255, 255, 255);
    /* 文字陰影 很 box-shadow一致 */
    text-shadow: 0px 0px 2px rgb(233, 233, 233),
                 0px 0px 3px rgb(134, 133, 131),
                 0px 0px 6px rgb(54, 54, 53);
}

/* 給主體內容區的樣式 */
.wrapper > .content {
    width: 100%;
    padding: 20px;
    box-sizing: border-box;
}

/* overflow:hidden可以清除浮動 */
.wrapper > .content > .list {
    overflow: hidden;
}

/* 給每個li的樣式 */
.wrapper > .content > .list > .item {
    width: 25%;
    text-align: center;
    padding: 10px;
    box-sizing: border-box;
    /* float: left; */
    display: inline-block;
}

/* 渲染進li中div的樣式 */
.wrapper > .content > .list > .item > div {
    background-color: rgba(87, 88, 88, 0.05);
    padding: 10px;
    box-sizing: border-box;
    cursor: pointer;
    margin-top: 15px;
}

/* 當渲染進li的div被hover時候產生的卡片效果 */
.wrapper > .content > .list > .item > div:hover {
    box-shadow: 2px 4px 10px rgb(155, 153, 153);
}

/* 每個li中後面會渲染進很多個div, 每個div中有一個img元素和一個
p元素 */
.wrapper > .content > .list > .item > div > p {
    position: relative;
    font-size: 20px;
    line-height: 30px;
    padding-bottom: 4px;
    color: #616060;
    /* border-bottom: 2px solid #ccc; */
}

/* 給p元素的下劃線設置樣式 */
.wrapper > .content > .list > .item > div > p::after {
    content: '';
    display: block;
    position: absolute;
    height: 2px;
    left: 45px;
    right: 45px;
    bottom: 0;
    background-image: linear-gradient(270deg, pink, gold, gold, pink);
}


.wrapper > .content > .list > .item > div > img {
    width: 100%;
}

/* 提示框 最下面的沒有更多數據了*/
.wrapper > .content > .toast {
    width: 100%;
    text-align: center;
    font-size: 24px;
    margin-top: 20px;
    color: #ccc;
}
  • JS(JS方法寫的有點亂, 太晚了, 腦袋不是很清晰, 可以自己根據邏輯減少耦合, 也用上了ES5的書寫方式, 更容易理解)
(function() {
    var curPage = 1; // 頁數, 因爲沒有後臺, 所以我們是通過mock數據來進行數據請求, 所以這個頁數必須由我們自己來控制了
    var toast = null; // 全局訪問的一個底層div用來顯示沒有更多數據了

    // 初始化方法
    function init() {
       
        getImagesData('./data.json', 'GET', function(resp) {  
            var data = JSON.parse(resp);
            var lastData = data.slice(0, curPage * 10);
            renderLi(lastData);
        }, curPage, true);
        bindEvent();
    }

    // 獲取數據
    function getImagesData(url, method, callback, data, flag) {
        ajax(url, method, callback, data, flag)
    }

    // 事件綁定
    function bindEvent() {
        // 監聽鼠標滾輪的滑動
        window.onscroll = function() {
           var scrollTop = document.documentElement.scrollTop; 
           var cHeight = document.documentElement.offsetHeight;
           var liMinHeight = getMinHeight().offsetHeight;
            // 對高度的計算
           if(liMinHeight + 140 < cHeight + scrollTop) {
               curPage ++;
               getImagesData('./data.json', 'GET', function(resp) {  
                var data = JSON.parse(resp);
                var lastData = data.slice((curPage - 1) * 10, curPage * 10);
                if(lastData.length === 0) {
                    if(toast) {
                        return toast;
                    }else {
                            toast = document.createElement('div');
                            toast.innerText = '沒有更多數據了';
                            toast.className = 'toast'
                            document.querySelector('.content').appendChild(toast);
                    }
                    return false;
                }else {
                    toast = null;
                }
                console.log(lastData);
                renderLi(lastData);
            }, curPage, true);
           }
        }
    }

    // 我們每次需要找到最小的li是哪個, 然後往最小的li進行數據的填充
    function getMinHeight() {
        var minIndex = 0; // 最小li的索引
        var lis = document.querySelectorAll('.item');
        var minHeight = lis[minIndex].offsetHeight;
        for(var i = 0, len = lis.length; i < len; i++) {
            if(minHeight > lis[i].offsetHeight) {
                minHeight = lis[i].offsetHeight;
                minIndex = i;
            }
        }

        return lis[minIndex];
    }

    // 抽離預加載函數
    function loadingReady(parentNode, imgNode, replaceNode) {
        (function(parentNode, imgNode){
            imgNode.onload = function() {
                // console.log(parentNode);
                // console.log(imgNode);
                // console.log(replaceNode);
                setTimeout(function() {
                    parentNode.replaceChild(imgNode,replaceNode);
                }, 500)
            }
        })(parentNode, imgNode)
    }

    // 渲染li
    function renderLi(lastData) {
        
            for(var i = 0, len = lastData.length; i < len; i++) {
                console.log(lastData[i]);
                var h = lastData[i].height; // 取到高度
                var url = lastData[i].url;  // 取到url
                var text = lastData[i].text;  // 取到文本
                var w = 247.5; 
                var liMinDom = getMinHeight();
                var img = new Image();
                var div = document.createElement('div'); // 預加載
                var p = document.createElement('p'); // 放置文字的容器
                var image = new Image(); // image標籤
                p.innerText = text;
                var container = document.createElement('div'); 
                container.className = 'container';
                div.style.width = w + 'px';
                div.style.height = h + 'px';
                div.style.backgroundColor = '#ccc';
                div.innerText = '資源正在加載中,請稍後';
                div.style.textAlign = 'center';
                div.style.lineHeight = h + 'px';
                div.style.fontSize = '20px';
                div.style.color = '#999';
                container.appendChild(div);
                container.appendChild(p);
                liMinDom.appendChild(container);
                image.height = h;
                image.src = url;

                loadingReady(container, image, container.querySelector('div'));
                
            }
    }


    init();

})()
  • 最後提供mock的數據格式
[
    {
        "url": "./images/pic4.jpg",
        "height": 535.89,
        "text": "巫師柴"
    },
    {
        "url": "./images/pic2.jpg",
        "height": 247.5,
        "text": "狗糧吃飽了~"
    },
    {
        "url": "./images/pic7.jpg",
        "height": 247.5,
        "text": "新年柴"
    },
    {
        "url":"./images/pic10.jpg",
        "height": 247.5,
        "text": "我就吃一勺, 應該不會胖吧"
    },
    {
        "url":"./images/pic12.jpg",
        "height": 258.22,
        "text": "抱魚柴"
    },
    {
        "url": "./images/pic8.jpg",
        "height": 154.69,
        "text": "仰望大佬"
    },
    {
        "url":"./images/pic11.png",
        "height": 280.42,
        "text": "我是一隻兇猛的大恐龍"
    },
    {
        "url": "./images/pic3.jpg",
        "height": 247.5,
        "text": "木乃伊柴, 家裏的衛生紙呢!!!?"
    },
    {
        "url": "./images/pic6.jpg",
        "height": 247.5,
        "text": "小肥柴過生日啦"
    },
    {
        "url":"./images/pic5.jpg",
        "height": 535.91,
        "text": "摸魚又被抓了"
    },
    {
        "url": "./images/pic9.jpg",
        "height": 154.69,
        "text": "瘦一百斤,肥"
    },
    {
        "url": "./images/pic14.png",
        "height": 240.94,
        "text": "柴晚安~"
    },
    {
        "url": "./images/pic1.jpg",
        "height": 247.5,
        "text": "點贊柴"
    },
    {
        "url": "./images/pic15.png",
        "height": 355.5,
        "text": "call me Iron Dog"
    },
    {
        "url": "./images/pic13.jpg",
        "height": "247.5",
        "text": "給大佬遞茶"
    },
    {
        "url": "./images/pic16.jpg",
        "height": "231.41",
        "text": "幻想成爲億萬富翁柴~"
    },
    {
        "url": "./images/pic17.jpeg",
        "height": "247.5",
        "text": "錢又花光了,555"
    },
    {
        "url": "./images/pic18.jpg",
        "height": "247.5",
        "text": "復柴者聯盟, 出動!!!"
    },
    {
        "url": "./images/pic18.png",
        "height": "236.55",
        "text": "聖誕小柴, 禮物送來~"
    },
    {
        "url": "./images/pic19.jpg",
        "height": "536.01",
        "text": "愚人節快樂,嘿嘿"
    },
    {
        "url": "./images/pic20.png",
        "height": "246.96",
        "text": "給你花花"
    },
    {
        "url": "./images/pic20.psng",
        "height": "426.6",
        "text": "逃課柴~溜了溜了"
    },
    {
        "url": "./images/pic21.jpg",
        "height": "247.5",
        "text": "柴元寶~~"
    },
    {
        "url": "./images/pic22.jpg",
        "height": "536.01",
        "text": "有錢就是這麼任性"
    },
    {
        "url": "./images/pic23.jpg",
        "height": "247.5",
        "text": "冬天來了, 多穿是福"
    },
    {
        "url": "./images/pic24.jpg",
        "height": "154.58",
        "text": "小柴寶寶摸摸頭"
    },
    {
        "url": "./images/pic25.jpg",
        "height": "247.5",
        "text": "啥~啥是秀恩愛~~"
    },
    {
        "url": "./images/pic26.jpg",
        "height": "536.01",
        "text": "該吃吃,該喝喝,啥事不往心裏擱的柴"
    },
    {
        "url": "./images/pic27.jpg",
        "height": "247.5",
        "text": "鳴人柴,看我螺旋雞腿~"
    },
    {
        "url": "./images/pic28.png",
        "height": "358.63",
        "text": "潮就一個字,柴只說一次"
    },
    {
        "url": "./images/pic29.png",
        "height": "155.56",
        "text": "行走江湖, 打抱不平柴"
    },
    {
        "url": "./images/pic31.png",
        "height": "308.04",
        "text": "柴社相聲"
    },
    {
        "url": "./images/pic40.png",
        "height": "202.46",
        "text": "狗團外賣, 生活不易, 客官別給差評哦~"
    }
]

至於圖片資源可以進我的github中下載,也可以自己找一些自己喜歡的圖片進行練習

至此, 感謝閱讀, 如果有意見和疑問隨時私信進行聯繫

發佈了28 篇原創文章 · 獲贊 11 · 訪問量 2043
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章