成品:
成品網址
成品功能: 1.在最上方 Filter posts 可以篩選貼文的內容 2.可以一直往下滑不需要換頁
HTML 上 CSS 之前的 HTML:
html 程式碼: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <body > <h1 > My Blog</h1 > <div class ="filter-container" > <input type="text" id="filter" class="filter" placeholder="Filter posts..." /> </div > <div id ="post-container" > <div class ="post" > <div class ="number" > 1</div > <div class ="post-info" > <h2 class ="post-title" > Post One</h2 > <p class ="post-body" > Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quasi nobis facere explicabo natus amet autem iusto dolore quam sapiente est! </p > </div > </div > <div class ="post" > <div class ="number" > 2</div > <div class ="post-info" > <h2 class ="post-title" > Post Two</h2 > <p class ="post-body" > Lorem ipsum dolor, sit amet consectetur adipisicing elit. Quasi nobis facere explicabo natus amet autem iusto dolore quam sapiente est! </p > </div > </div > </div > <div class ="loader" > <div class ="circle" > </div > <div class ="circle" > </div > <div class ="circle" > </div > </div > <script src ="script.js" > </script > </body >
CSS: CSS 設置好的樣式
CSS 完整程式碼 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 @import url('https:fonts.googleapis.com/css?family=Roboto&display=swap' ); * { box-sizing : border-box; } body { background-color : #296ca8 ; color : #fff ; font-family : 'Roboto' , sans-serif; display : flex; flex-direction : column; align-items : center; justify-content : center; min-height : 100vh ; margin : 0 ; padding-bottom : 100px ; } h1 { margin-bottom : 0 ; text-align : center; } .filter-container { margin-top : 20px ; width : 80vw ; max-width : 800px ; } .filter { width : 100% ; padding : 12px ; } .post { position : relative; background-color : #4992d3 ; box-shadow : 0 2px 4px rgba (0 , 0 , 0 , 0.3 ); border-radius : 3px ; padding : 20px ; margin : 40px 0 ; display : flex; width : 80vw ; max-width : 800px ; } .post .post-title { margin : 0 ; }cl3 .post .post-body { margin : 15px 0 0 ; line-height : 1.3 ; } .post .post-info { margin-left : 20px ; } .post .number { position : absolute; top : -15px ; left : -15px ; font-size : 15px ; width : 40px ; height : 40px ; border-radius : 50% ; background : #fff ; color : #296ca8 ; display : flex; align-items : center; justify-content : center; padding : 7px 10px ; } .loader { opacity : 0 ; display : flex; position : fixed; bottom : 50px ; transition : opacity 0.3s ease-in; } .loader .show { opacity : 1 ; } .circle { background-color : #fff ; width : 10px ; height : 10px ; border-radius : 50% ; margin : 5px ; animation : bounce 0.5s ease-in infinite; } .circle :nth-of-type (2 ) { animation-delay : 0.1s ; } .circle :nth-of-type (3 ) { animation-delay : 0.2s ; } @keyframes bounce { 0% , 100% { transform : translateY (0 ); } 50% { transform : translateY (-10px ); } }
小補充:
無
JS: 變數設置 1 2 3 4 5 6 7 const postsContainer = document .getElementById('posts-container' );const loading = document .querySelector('.loader' );const filter = document .getElementById('filter' );let limit = 5 ; let page = 1 ;
Functions 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 async function getPosts ( ) { const res = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=${limit} &_page=${page} ` ); const data = await res.json(); return data; } async function showPosts ( ) { const posts = await getPosts(); posts.forEach(post => { const postEl = document .createElement('div' ); postEl.classList.add('post' ); postEl.innerHTML = ` <div class="number">${post.id} </div> <div class="post-info"> <h2 class="post-title">${post.title} </h2> <p class="post-body">${post.body} </p> </div> ` ; postsContainer.appendChild(postEl); }); } function showLoading ( ) { loading.classList.add('show' ); setTimeout (() => { loading.classList.remove('show' ); setTimeout (() => { page++; showPosts(); }, 300 ); }, 1000 ); } function filterPosts (e ) { const term = e.target.value.toUpperCase(); const posts = document .querySelectorAll('.post' ); posts.forEach(post => { const title = post.querySelector('.post-title' ).innerText.toUpperCase(); const body = post.querySelector('.post-body' ).innerText.toUpperCase(); if (title.indexOf(term) > -1 || body.indexOf(term) > -1 ) { post.style.display = 'flex' ; } else { post.style.display = 'none' ; } }); } showPosts();
事件監聽 監聽卷軸(scroll) 當捲動卷軸時觸發事件 監聽 filter 區域 當 input 區域輸入文字時觸發事件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 window .addEventListener('scroll' , () => { const { scrollTop, scrollHeight, clientHeight } = document .documentElement; console .log(document .documentElement.scrollTop); if (scrollTop + clientHeight >= scrollHeight - 5 ) { showLoading(); } }); filter.addEventListener('input' , filterPosts);
小補充:
這三個部分出現在無限卷軸的條件裡面:
所以判斷式裡面只要 scrollTOP+clientHeight>=scrollHeight 意思是
一般來說 scrollHeight = scrollTop + clientHeight
所以當往下滑的距離()超過原本卷軸也就是 srollHeight 5(不一定要 5 只要超過就好)的時候會觸發產生新的 posts
這張圖片代表 scrollTOP 也就是你能拉多少距離加上所有 post 的 div 的高度= 下滑距離
Async 筆記連結
JS 完整程式碼 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 const postsContainer = document .getElementById('posts-container' );const loading = document .querySelector('.loader' );const filter = document .getElementById('filter' );let limit = 5 ; let page = 1 ; async function getPosts ( ) { const res = await fetch( `https://jsonplaceholder.typicode.com/posts?_limit=${limit} &_page=${page} ` ); const data = await res.json(); return data; } async function showPosts ( ) { const posts = await getPosts(); posts.forEach(post => { const postEl = document .createElement('div' ); postEl.classList.add('post' ); postEl.innerHTML = ` <div class="number">${post.id} </div> <div class="post-info"> <h2 class="post-title">${post.title} </h2> <p class="post-body">${post.body} </p> </div> ` ; postsContainer.appendChild(postEl); }); } function showLoading ( ) { loading.classList.add('show' ); setTimeout (() => { loading.classList.remove('show' ); setTimeout (() => { page++; showPosts(); }, 300 ); }, 1000 ); } function filterPosts (e ) { const term = e.target.value.toUpperCase(); const posts = document .querySelectorAll('.post' ); posts.forEach(post => { const title = post.querySelector('.post-title' ).innerText.toUpperCase(); const body = post.querySelector('.post-body' ).innerText.toUpperCase(); if (title.indexOf(term) > -1 || body.indexOf(term) > -1 ) { post.style.display = 'flex' ; } else { post.style.display = 'none' ; } }); } showPosts(); window .addEventListener('scroll' , () => { const { scrollTop, scrollHeight, clientHeight } = document .documentElement; console .log(document .documentElement.scrollTop); if (scrollTop + clientHeight >= scrollHeight - 5 ) { showLoading(); } }); filter.addEventListener('input' , filterPosts);