kodingup.camp

Beranda/HTML, CSS & SCSS Mastery

Styling Visual Tingkat Lanjut

Modul 5 dari 114 menit bacaAdvanced Visual Styling

Isi modul

    Kenapa ini penting

    Pengguna menilai kualitas sebuah aplikasi dari hal-hal kecil: tombol yang terasa empuk saat ditekan, kartu yang sedikit terangkat saat disentuh, notifikasi yang meluncur masuk alih-alih muncul tiba-tiba. Gerakan yang halus membantu otak memahami perubahan: dari mana sesuatu datang dan ke mana ia pergi. Di modul ini kamu akan belajar alat visual tingkat lanjut CSS (transition, transform, animation, shadow, gradient, filter) sekaligus CSS custom properties yang membuat semua nilai desain bisa diatur dari satu tempat, termasuk untuk dark mode.

    Konsep inti

    Transition

    Transition membuat perubahan nilai properti berlangsung bertahap. Tanpa transition, :hover langsung meloncat. Dengan transition, perubahan bergerak mulus.

    .tombol { transition: background-color 200ms ease, transform 200ms ease; }
    

    Bagiannya: properti apa, durasi, fungsi waktu (ease, ease-in, ease-out, linear, atau cubic-bezier(...)), dan opsional delay. Untuk interaksi UI, durasi 150 sampai 300ms terasa responsif. Hindari transition: all karena ikut menganimasikan properti yang tidak kamu sadari dan bisa boros performa.

    Transform

    Transform mengubah tampilan elemen tanpa mengubah tata letak di sekitarnya (elemen lain tidak ikut bergeser):

    CSS modern juga punya properti individual translate, scale, dan rotate yang bisa ditulis terpisah, misalnya scale: 1.05;.

    Tips performa: properti transform dan opacity bisa dianimasikan browser dengan murah karena tidak memicu perhitungan ulang layout. Menganimasikan width, height, top, atau margin memaksa browser menghitung ulang posisi elemen lain di setiap frame dan bisa membuat animasi patah-patah di HP.

    Animation dan @keyframes

    Kalau transition butuh pemicu (misalnya hover), animation bisa berjalan sendiri dan punya banyak tahap.

    @keyframes denyut {
      0%, 100% { transform: scale(1); }
      50%      { transform: scale(1.1); }
    }
    .badge { animation: denyut 1.5s ease-in-out infinite; }
    

    Properti animasi: animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count (angka atau infinite), animation-direction (alternate untuk bolak-balik), dan animation-fill-mode (forwards menahan keadaan akhir, both juga menerapkan keadaan awal selama delay).

    Aksesibilitas gerak: sebagian orang mengalami pusing atau mual karena animasi. Hormati pengaturan sistem mereka:

    @media (prefers-reduced-motion: reduce) {
      *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
      }
    }
    

    Shadow

    Gradient

    Gradient adalah gambar yang dibuat browser, jadi dipasang lewat background atau background-image:

    Trik teks bergradasi: background: linear-gradient(...); background-clip: text; color: transparent;.

    CSS custom properties (variabel CSS)

    Custom property adalah variabel yang hidup di browser. Didefinisikan dengan awalan dua strip dan dibaca dengan var():

    :root {
      --warna-utama: hsl(221 83% 53%);
      --radius: 12px;
    }
    .tombol { background: var(--warna-utama); border-radius: var(--radius); }
    

    Kelebihannya dibanding variabel Sass (yang akan kamu pelajari nanti):

    Inilah dasar dark mode modern: definisikan warna sebagai variabel, lalu timpa nilainya di dalam @media (prefers-color-scheme: dark) atau pada atribut [data-theme="dark"].

    Filter

    Contoh kode

    <!DOCTYPE html>
    <html lang="id">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Kartu Interaktif</title>
      <style>
        :root {
          --bg: hsl(220 30% 97%);
          --surface: white;
          --teks: hsl(222 47% 11%);
          --utama: hsl(245 75% 60%);
          --aksen: hsl(330 80% 60%);
          --radius: 16px;
          --bayangan: 0 1px 2px rgb(0 0 0 / 0.06), 0 8px 24px -8px rgb(0 0 0 / 0.15);
          --durasi: 220ms;
        }
    
        /* Dark mode: cukup timpa variabelnya */
        @media (prefers-color-scheme: dark) {
          :root {
            --bg: hsl(222 40% 8%);
            --surface: hsl(222 30% 14%);
            --teks: hsl(210 40% 96%);
            --bayangan: 0 8px 24px -8px rgb(0 0 0 / 0.6);
          }
        }
    
        body {
          margin: 0;
          min-height: 100vh;
          display: grid;
          place-items: center;
          font-family: system-ui, sans-serif;
          color: var(--teks);
          background: var(--bg);
        }
    
        .kartu {
          width: min(22rem, 90vw);
          padding: 1.5rem;
          background: var(--surface);
          border-radius: var(--radius);
          box-shadow: var(--bayangan);
          transition: transform var(--durasi) ease, box-shadow var(--durasi) ease;
        }
        .kartu:hover {
          transform: translateY(-6px);  /* hanya transform: murah untuk browser */
          box-shadow: 0 20px 40px -12px rgb(0 0 0 / 0.3);
        }
    
        .judul {
          margin: 0 0 0.5rem;
          font-size: 1.75rem;
          background: linear-gradient(135deg, var(--utama), var(--aksen));
          -webkit-background-clip: text; /* masih dibutuhkan sebagian browser berbasis WebKit */
          background-clip: text;
          color: transparent;
        }
    
        .foto {
          width: 100%;
          height: auto;
          aspect-ratio: 16 / 9;
          object-fit: cover;
          border-radius: calc(var(--radius) - 6px);
          filter: grayscale(1);
          transition: filter var(--durasi) ease;
        }
        .kartu:hover .foto { filter: grayscale(0); }  /* warna muncul saat hover */
    
        .badge {
          display: inline-block;
          padding: 0.2rem 0.7rem;
          font-size: 0.8rem;
          color: white;
          background: var(--aksen);
          border-radius: 9999px;
          animation: denyut 1.6s ease-in-out infinite;
        }
        @keyframes denyut {
          0%, 100% { transform: scale(1); }
          50%      { transform: scale(1.08); }
        }
    
        .muncul { animation: masuk 600ms ease-out both; }
        @keyframes masuk {
          from { opacity: 0; transform: translateY(20px); }
          to   { opacity: 1; transform: translateY(0); }
        }
    
        @media (prefers-reduced-motion: reduce) {
          *, *::before, *::after {
            animation-duration: 0.01ms !important;
            animation-iteration-count: 1 !important;
            transition-duration: 0.01ms !important;
          }
        }
      </style>
    </head>
    <body>
      <article class="kartu muncul">
        <img class="foto" src="https://images.unsplash.com/photo-1509042239860-f550ce710b93?w=800"
             alt="Secangkir kopi hitam di atas meja" width="800" height="450">
        <h2 class="judul">Kopi Pagi</h2>
        <span class="badge">Promo</span>
        <p>Arahkan kursor ke kartu untuk melihat efeknya.</p>
      </article>
    </body>
    </html>
    <!-- → Kartu meluncur masuk dari bawah saat halaman dibuka.
         Hover: kartu terangkat 6px, bayangan menebal, foto berubah dari hitam-putih ke berwarna.
         Ganti OS ke dark mode → seluruh warna berganti otomatis tanpa mengubah CSS komponen. -->
    

    Kesalahan umum

    1. transition: all. Semua properti ikut beranimasi, termasuk yang tidak diinginkan. Cara benar: sebutkan properti spesifik.
    2. Menganimasikan width, left, atau margin untuk efek gerak. Cara benar: pakai transform dan opacity.
    3. Mengabaikan prefers-reduced-motion. Cara benar: matikan atau kurangi animasi untuk pengguna yang memintanya.
    4. Menulis variabel tanpa var(), misalnya color: --utama;. Cara benar: color: var(--utama);.
    5. Bayangan terlalu pekat seperti box-shadow: 5px 5px 5px black. Cara benar: blur besar, offset kecil, warna transparan.

    Latihan

    1. Mudah, Buat tombol yang membesar 5% dengan transisi 200ms saat di-hover.
    2. Sedang, Buat loader berbentuk lingkaran berputar memakai border, border-radius, dan @keyframes dengan rotate.
    3. Menantang, Buat sistem tema dengan custom properties: tema terang di :root, tema gelap di [data-theme="dark"], dan tombol JavaScript yang mengganti atribut data-theme pada <html>.
    Lihat solusi
    /* Soal 1 */
    .btn-zoom {
      padding: 0.75rem 1.5rem;
      transition: transform 200ms ease;
    }
    .btn-zoom:hover { transform: scale(1.05); }
    
    /* Soal 2 */
    .loader {
      width: 48px;
      height: 48px;
      border: 5px solid hsl(220 20% 85%);
      border-top-color: hsl(245 75% 60%); /* hanya satu sisi berwarna, jadi terlihat berputar */
      border-radius: 50%;
      animation: putar 0.8s linear infinite;
    }
    @keyframes putar {
      to { transform: rotate(360deg); }
    }
    
    <!-- Soal 3 -->
    <html lang="id" data-theme="light">
    <head>
      <style>
        :root {
          --bg: #ffffff;
          --teks: #111827;
        }
        [data-theme="dark"] {
          --bg: #0f172a;
          --teks: #f1f5f9;
        }
        body {
          background: var(--bg);
          color: var(--teks);
          transition: background-color 300ms ease, color 300ms ease;
        }
      </style>
    </head>
    <body>
      <button id="ganti-tema" type="button">Ganti tema</button>
      <script>
        const root = document.documentElement;
        document.getElementById('ganti-tema').addEventListener('click', () => {
          const gelap = root.dataset.theme === 'dark';
          root.dataset.theme = gelap ? 'light' : 'dark';
          // → klik pertama: halaman jadi gelap, klik kedua: kembali terang
        });
      </script>
    </body>
    </html>
    

    Ringkasan

    Kuis pemahaman

    Pilih satu jawaban per soal. Penjelasan muncul setelah kamu menjawab.