├── 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 |
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 |
--------------------------------------------------------------------------------