Sticky Element - JavaScript & CSS
Published: 2024-06-09 08:08:25

Halo semua, 2 bulan ini absen posting dikarenakan kesibukan yang padat. Mari kita mulai belajar lagi seputar JavaScript dan CSS, kali ini kita akan membahas dan membuat _sticky element_. _Sticky element_ paling sering kita temukan pada navigation bar sebuah website, ada banyak cara untuk membuat sticky element, pada postingan kali ini kita akan membuat dengan 2 cara berbeda. Langsung saja kita buat file index.html, app.js dan style.css

index.html

```html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Sticky Navigation</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <header>
      <div class="nav">
        <div class="logo">LOGO</div>
        <ul class="menu">
          <li><a href="#" class="section-1">Section 1</a></li>
          <li><a href="#" class="section-2">Section 2</a></li>
          <li><a href="#" class="section-3">Section 3</a></li>
          <li><a href="#" class="section-4">Section 4</a></li>
        </ul>
      </div>
      <div class="title">
        <h1>This is header</h1>
      </div>
    </header>
    <section class="section" id="section-1">This is section 1</section>
    <section class="section" id="section-2">This is section 2</section>
    <section class="section" id="section-3">This is section 3</section>
    <section class="section" id="section-4">This is section 4</section>

    <script src="app.js"></script>
  </body>
</html>

```

style.css

```css
/* file: style.css */

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.title {
  height: 900px;
  background-color: cornflowerblue;
}

.nav {
  height: 50px;
  width: 100%;
  background-color: aqua;
  display: flex;
  justify-content: space-between;
  align-items: center;
  font-weight: bold;
}

.nav .logo {
  padding-left: 20px;
}

.nav ul {
  display: flex;
}

.nav ul li {
  list-style: none;
  padding-right: 20px;
}

.nav ul li a {
  text-decoration: none;
}

.section {
  height: 900px;
  font-size: 50px;
}

.section:nth-child(odd) {
  background-color: greenyellow;
}

.section:nth-child(even) {
  background-color: burlywood;
}

.nav.sticky {
  position: fixed;
}

```

## Sticky Element Cara Pertama
Kita isi dulu file app.js dengan implementasi smooth scrolling pada materi yang pernah kita bahas [disini](https://dev.to/boibolang/smooth-scrolling-aj1)

```javascript
// file: app.js

const selectors = {
  linkSection1: document.querySelector('.section-1'),
  linkSection2: document.querySelector('.section-2'),
  linkSection3: document.querySelector('.section-3'),
  linkSection4: document.querySelector('.section-4'),
  section1: document.querySelector('#section-1'),
  section2: document.querySelector('#section-2'),
  section3: document.querySelector('#section-3'),
  section4: document.querySelector('#section-4'),
  navbar: document.querySelector('.nav'),
};

// Smooth scrolling
selectors.linkSection1.addEventListener('click', () => {
  selectors.section1.scrollIntoView({ behavior: 'smooth' });
});
selectors.linkSection2.addEventListener('click', () => {
  selectors.section2.scrollIntoView({ behavior: 'smooth' });
});
selectors.linkSection3.addEventListener('click', () => {
  selectors.section3.scrollIntoView({ behavior: 'smooth' });
});
selectors.linkSection4.addEventListener('click', () => {
  selectors.section4.scrollIntoView({ behavior: 'smooth' });
});
```
Hasilnya kurang lebih sebagai berikut

![file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v2meevxh8wff9dxd0vgg.gif)

Kita ingin pada saat masuk ke Section 1 dan seterusnya, navigation bar-nya muncul. Kurang lebih logic-nya sebagai berikut :

1. Dapatkan koordinat atas (top) Section 1 terhadap koordinat window
2. Jika koordinat window lebih dari koordinat atas (top) Section 1 maka munculkan navigation bar

Mari kita implementasikan pada file app.js

```javascript
// Sticky navbar cara pertama
// 1. Sticky navigation muncul pada saat section 1 tampil, maka harus kita dapatkan koordinat Y section 1
const section1Coord = selectors.section1.getBoundingClientRect();
console.log('Section 1 top:', section1Coord.top);

// 2.Jika tampilan koordinat Y window lebih dari koordinat Y section 1 maka sticky nav muncul
window.addEventListener('scroll', () => {
  console.log(window.scrollY);
  if (window.scrollY > section1Coord.top) selectors.navbar.classList.add('sticky');
  else selectors.navbar.classList.remove('sticky');
});
```
Jika kita inspect halaman, berapa sih koordinat atas (top) section 1 sebenarnya ? Dari kode diatas didapatkan bahwa jarak atas (top) Section 1 terhadap window adalah 950 pixel. Hal ini sebenarnya bisa kita hitung dari tinggi header (900 px) + tinggi navigation bar (50 px).


![file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jjsomy392us88r2np9h0.png)

Maka dengan logic diatas jika window scroll lebih dari 950 pixel maka navigation bar akan muncul, hasilnya sebagai berikut

![file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bkj4c5f8ed0mytyydzhi.gif)

Metode ini tidak cocok dipakai untuk skala besar karena scroll akan aktif terus menerus selama halaman diakses, dapat menyebabkan performance issue.

## Sticky Element Dengan Intersection Observer API
Apa pula itu Intersection Observer API ? Berikut penjelasan dari website MDN

> The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.

Penjelasan singkatnya adalah intersection observer API akan memonitor posisi target terhadap posisi dokumen. Prakteknya seperti apa ? Mari kita bahas.
Syntax intersection observer API adalah sebagai berikut

`const observer = new IntersectionObserver(obsCallback, obsOptions)`. 

_opsCallback_ adalah fungsi/callback yang akan dieksekusi, sedangkan obsOptions adalah parameter yang digunakan.

obsOptions memiliki beberapa parameter

```javascript
obsOptions = {
  root: 0,
  threshold: 0.1,
  rootMargin: '-50px'
}
```
_root_ : adalah posisi dimana intersection dengan target terjadi, jika diisi dengan _null_ artinya terhadap top-level viewport
_threshold_ : adalah kapan akan muncul aksinya, bisa diisi dengan nilai tungal (dalam %) atau array. Jika kita isi 0.1 (sama dengan 10%) maka aksi akan muncul pada jarak 10% pada saat intersection terjadi atau 10% sebelum intersection berakhir
_rootMargin_ : adalah batas (atas-bawah : height) dari element yang beririsan

Lengkapnya seperti ini

```javascript
// Sticky navbar menggunakan intersection observer API

const obsCallback = (entries) => {
  entries.forEach((entry) => {
    console.log(entry);
  });
};
const obsOption = {
  root: null,
  threshold: 0.1,
  rootMargin: '-50px',
};
const observer = new IntersectionObserver(obsCallback, obsOption);
observer.observe(selectors.section1);
```
Perhatikan posisi berikut

![file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nmh7va99dqqh5o97x5q1.png)

Pada saat Section 1 masuk viewport sebesar 0.1 viewport, observer akan mendeteksi. Property yang penting untuk diingat adalah _isIntersection_ dan _intersectionRatio_, pada console nilainya adalah _true (isIntersection)_ dan _0.10105444490909576 (intersectionRatio)_. Jika kita scroll terus sebelum Section 1 habis sebesar 0.1 viewport, observer akan mendeteksi perubahan tapi kali ini nilai dari _isIntersection_ adalah _false_ dan _intersectionRatio_ adalah _0.09617595374584198_

![file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/diblt1xfnqmentsd46yt.png)

Logic untuk implementasi kira-kira seperti ini
Kapan kita ingin navigation bar muncul ? Pada saat header sudah tidak terlihat, ini berarti property _isIntersection_ bernilai _false_

```javascript
// Sticky navigation bar menggunakan Intersection Observer API

const header = document.querySelector('header');
const navHeight = selectors.navbar.getBoundingClientRect().height;
const stickyNav = (entries) => {
  const [entry] = entries;

  if (!entry.isIntersecting) selectors.navbar.classList.add('sticky');
  else selectors.navbar.classList.remove('sticky');
};

const headerObserver = new IntersectionObserver(stickyNav, {
  root: null,
  threshold: 0,
  rootMargin: `-${navHeight}px`,
});
headerObserver.observe(header);
```
Hasilnya sebagai berikut

![file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kadw7kb8ceu2dqc0yzwb.gif)

_Note : console log untuk intersection observer gambarnya kurang jelas, bisa dicoba sendiri di browser masing-masing_