├── README.md ├── index.html ├── script.js └── style.css /README.md: -------------------------------------------------------------------------------- 1 | ## Infinite Scrolling & Filter 2 | 3 | [Demo](https://alvar91.github.io/infinite-scroll-blog-html-css-js/) Display blog posts from [jsonplaceholder](https://jsonplaceholder.typicode.com) and add infinite scroll to fetch posts and also add filter box 4 | 5 | ## Project Specifications 6 | 7 | - Create UI & custom CSS loader animation 8 | - Fetch initial posts from API and display 9 | - Scroll down, show loader and fetch next set of posts 10 | - Add filtering for fetched posts 11 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | My Blog 9 | 10 | 11 |

My Blog

12 | 13 |
14 | 20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /script.js: -------------------------------------------------------------------------------- 1 | const postsContainer = document.getElementById('posts-container'); 2 | const loading = document.querySelector('.loader'); 3 | const filter = document.getElementById('filter'); 4 | 5 | let limit = 5; 6 | let page = 1; 7 | 8 | // Fetch posts from API 9 | async function getPosts() { 10 | const res = await fetch( 11 | `https://jsonplaceholder.typicode.com/posts?_limit=${limit}&_page=${page}` 12 | ); 13 | 14 | const data = await res.json(); 15 | 16 | return data; 17 | } 18 | 19 | // Show posts in DOM 20 | async function showPosts() { 21 | const posts = await getPosts(); 22 | 23 | posts.forEach(post => { 24 | const postEl = document.createElement('div'); 25 | postEl.classList.add('post'); 26 | postEl.innerHTML = ` 27 |
${post.id}
28 |
29 |

${post.title}

30 |

${post.body}

31 |
32 | `; 33 | 34 | postsContainer.appendChild(postEl); 35 | }); 36 | } 37 | 38 | // Show loader & fetch more posts 39 | function showLoading() { 40 | loading.classList.add('show'); 41 | 42 | setTimeout(() => { 43 | loading.classList.remove('show'); 44 | 45 | setTimeout(() => { 46 | page++; 47 | showPosts(); 48 | }, 300); 49 | }, 1000); 50 | } 51 | 52 | // Filter posts by input 53 | function filterPosts(e) { 54 | const term = e.target.value.toUpperCase(); 55 | const posts = document.querySelectorAll('.post'); 56 | 57 | posts.forEach(post => { 58 | const title = post.querySelector('.post-title').innerText.toUpperCase(); 59 | const body = post.querySelector('.post-body').innerText.toUpperCase(); 60 | 61 | if (title.indexOf(term) > -1 || body.indexOf(term) > -1) { 62 | post.style.display = 'flex'; 63 | } else { 64 | post.style.display = 'none'; 65 | } 66 | }); 67 | } 68 | 69 | // Show initial posts 70 | showPosts(); 71 | 72 | window.addEventListener('scroll', () => { 73 | const { scrollTop, scrollHeight, clientHeight } = document.documentElement; 74 | 75 | if (scrollTop + clientHeight >= scrollHeight - 5) { 76 | showLoading(); 77 | } 78 | }); 79 | 80 | filter.addEventListener('input', filterPosts); 81 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Roboto&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | background-color: #296ca8; 9 | color: #fff; 10 | font-family: 'Roboto', sans-serif; 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | justify-content: center; 15 | min-height: 100vh; 16 | margin: 0; 17 | padding-bottom: 100px; 18 | } 19 | 20 | h1 { 21 | margin-bottom: 0; 22 | text-align: center; 23 | } 24 | 25 | .filter-container { 26 | margin-top: 20px; 27 | width: 80vw; 28 | max-width: 800px; 29 | } 30 | 31 | .filter { 32 | width: 100%; 33 | padding: 12px; 34 | font-size: 16px; 35 | } 36 | 37 | .post { 38 | position: relative; 39 | background-color: #4992d3; 40 | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); 41 | border-radius: 3px; 42 | padding: 20px; 43 | margin: 40px 0; 44 | display: flex; 45 | width: 80vw; 46 | max-width: 800px; 47 | } 48 | 49 | .post .post-title { 50 | margin: 0; 51 | } 52 | 53 | .post .post-body { 54 | margin: 15px 0 0; 55 | line-height: 1.3; 56 | } 57 | 58 | .post .post-info { 59 | margin-left: 20px; 60 | } 61 | 62 | .post .number { 63 | position: absolute; 64 | top: -15px; 65 | left: -15px; 66 | font-size: 15px; 67 | width: 40px; 68 | height: 40px; 69 | border-radius: 50%; 70 | background: #fff; 71 | color: #296ca8; 72 | display: flex; 73 | align-items: center; 74 | justify-content: center; 75 | padding: 7px 10px; 76 | } 77 | 78 | .loader { 79 | opacity: 0; 80 | display: flex; 81 | position: fixed; 82 | bottom: 50px; 83 | transition: opacity 0.3s ease-in; 84 | } 85 | 86 | .loader.show { 87 | opacity: 1; 88 | } 89 | 90 | .circle { 91 | background-color: #fff; 92 | width: 10px; 93 | height: 10px; 94 | border-radius: 50%; 95 | margin: 5px; 96 | animation: bounce 0.5s ease-in infinite; 97 | } 98 | 99 | .circle:nth-of-type(2) { 100 | animation-delay: 0.1s; 101 | } 102 | 103 | .circle:nth-of-type(3) { 104 | animation-delay: 0.2s; 105 | } 106 | 107 | @keyframes bounce { 108 | 0%, 109 | 100% { 110 | transform: translateY(0); 111 | } 112 | 113 | 50% { 114 | transform: translateY(-10px); 115 | } 116 | } 117 | --------------------------------------------------------------------------------