kodingup.camp

Beranda/HTML, CSS & SCSS Mastery

Teknik Positioning Responsif

Modul 8 dari 115 menit bacaResponsive Positioning Techniques

Isi modul

    Kenapa ini penting

    Sebagian besar orang Indonesia membuka internet lewat HP. Halaman yang tampil cantik di laptop tetapi berantakan di layar 360px akan langsung ditinggalkan. Di modul ini kamu belajar dua keterampilan yang saling melengkapi: positioning (menaruh elemen di luar aliran normal, misalnya navbar lengket, badge di pojok kartu, modal di atas segalanya) dan teknik responsif (media query mobile-first, container query, dan clamp() untuk ukuran yang mengalir mulus).

    Konsep inti

    Lima nilai position

    Nilai Tetap di aliran dokumen? Patokan posisi Kegunaan khas
    static Ya Tidak ada (default) Semua elemen normal
    relative Ya, ruang aslinya tetap dipesan Posisi aslinya sendiri Geser sedikit; jadi patokan untuk anak absolute
    absolute Tidak, keluar dari aliran Leluhur terdekat yang bukan static Badge, ikon di dalam input, dropdown
    fixed Tidak Viewport (layar) Tombol chat melayang, navbar tetap
    sticky Ya, sampai ambang tercapai Kontainer scroll terdekat Header tabel, navbar yang menempel saat scroll

    Properti top, right, bottom, left (atau singkatannya inset) hanya berpengaruh kalau position bukan static.

    Pola paling sering: induk relative, anak absolute. Induk menjadi "bingkai patokan", anak ditaruh di koordinat tertentu di dalam bingkai itu. Kalau lupa memberi relative pada induk, anak akan mencari leluhur yang lebih jauh, sering kali sampai ke halaman itu sendiri, dan muncul di pojok layar.

    sticky butuh nilai ambang seperti top: 0 supaya bekerja. Ia juga tidak berfungsi kalau salah satu leluhurnya punya overflow: hidden atau overflow: auto yang bukan kontainer scroll yang kamu maksud.

    z-index dan stacking context

    z-index mengatur siapa di atas siapa ketika elemen bertumpuk. Nilainya hanya berlaku pada elemen yang ber-position (bukan static), atau pada flex/grid item.

    Konsep yang sering membingungkan adalah stacking context. Bayangkan setiap stacking context sebagai map plastik berisi kertas. Kertas di dalam map A dengan z-index: 9999 tetap berada di bawah map B kalau map A sendiri ditumpuk di bawah map B. Stacking context baru terbentuk oleh, antara lain, elemen ber-position dengan z-index selain auto, elemen dengan opacity kurang dari 1, transform, filter, atau isolation: isolate.

    Tips: buat skala z-index yang terbatas (misalnya 10 dropdown, 100 header, 1000 modal) daripada angka acak seperti 99999.

    Media query mobile-first

    Media query menerapkan CSS hanya ketika kondisi tertentu terpenuhi.

    Mobile-first berarti gaya dasar ditulis untuk layar kecil, lalu ditambah untuk layar lebih besar dengan min-width:

    .grid { display: grid; gap: 1rem; }               /* HP: satu kolom */
    @media (min-width: 48rem) { .grid { grid-template-columns: 1fr 1fr; } }      /* ≥768px */
    @media (min-width: 64rem) { .grid { grid-template-columns: repeat(3, 1fr); } } /* ≥1024px */
    

    Kenapa mobile-first? Layar kecil biasanya layout paling sederhana (satu kolom), jadi CSS-nya lebih sedikit. Perangkat kecil yang sering lebih lambat tidak perlu memproses aturan desktop lalu menimpanya.

    Sintaks range modern juga didukung browser modern: @media (width >= 48rem) { ... }. Media query lain yang berguna: (orientation: landscape), (hover: hover) untuk perangkat yang benar-benar punya kursor, (prefers-color-scheme: dark), dan (prefers-reduced-motion: reduce).

    Breakpoint sebaiknya ditentukan oleh konten, bukan merek HP. Perlebar jendela browser pelan-pelan; saat layout mulai terlihat janggal, di situlah breakpoint.

    Container query

    Media query bertanya "seberapa lebar layarnya?". Container query bertanya "seberapa lebar wadah tempat komponen ini berada?". Ini penting untuk komponen yang dipakai ulang: kartu yang sama bisa berada di sidebar sempit atau di area utama yang lebar.

    .wadah-kartu { container-type: inline-size; container-name: kartu; }
    
    @container kartu (min-width: 400px) {
      .kartu { display: grid; grid-template-columns: 120px 1fr; }
    }
    

    Elemen yang diberi container-type menjadi kontainer; aturan @container berlaku untuk keturunannya, bukan kontainer itu sendiri. Ada juga unit container seperti cqi (1% lebar inline kontainer).

    clamp(): ukuran fluid

    clamp(MIN, IDEAL, MAX) mengembalikan nilai IDEAL, tetapi tidak pernah di bawah MIN atau di atas MAX.

    h1 { font-size: clamp(1.75rem, 1rem + 4vw, 3.5rem); }
    

    Di HP judul sekitar 28px, di layar lebar maksimal 56px, dan di antaranya tumbuh mulus mengikuti lebar layar, tanpa satu media query pun. Selalu sertakan komponen rem di nilai tengah (bukan hanya vw) agar teks tetap ikut membesar saat pengguna memperbesar ukuran font. Fungsi saudaranya: min() dan max(), misalnya width: min(100% - 2rem, 70rem) untuk kontainer yang punya jarak tepi di HP dan berhenti di 70rem pada layar lebar.

    Contoh kode

    <!DOCTYPE html>
    <html lang="id">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <title>Toko Kopi Responsif</title>
      <style>
        *, *::before, *::after { box-sizing: border-box; }
        body { margin: 0; font-family: system-ui, sans-serif; line-height: 1.5; }
    
        /* Skala z-index terpusat */
        :root { --z-header: 100; --z-fab: 200; }
    
        /* Navbar menempel di atas saat scroll */
        .header {
          position: sticky;
          top: 0;
          z-index: var(--z-header);
          padding: 0.75rem 1rem;
          background: rgb(255 255 255 / 0.9);
          backdrop-filter: blur(8px);
          border-bottom: 1px solid #e5e7eb;
        }
    
        /* Kontainer dengan jarak tepi di HP, maksimal 70rem di desktop */
        .kontainer { width: min(100% - 2rem, 70rem); margin-inline: auto; }
    
        /* Judul fluid */
        .judul { font-size: clamp(1.75rem, 1rem + 4vw, 3.5rem); line-height: 1.1; }
    
        /* Mobile-first: satu kolom dulu */
        .layout { display: grid; gap: 1.5rem; }
        @media (min-width: 48rem) {
          .layout { grid-template-columns: 2fr 1fr; } /* tablet ke atas: konten + sidebar */
        }
    
        /* Setiap sel produk adalah container */
        .produk { container-type: inline-size; }
    
        .kartu {
          position: relative;               /* patokan untuk badge */
          padding: 1rem;
          border: 1px solid #e5e7eb;
          border-radius: 12px;
        }
        .kartu__badge {
          position: absolute;
          top: 0.5rem;
          right: 0.5rem;
          padding: 0.1rem 0.5rem;
          font-size: 0.75rem;
          color: white;
          background: #dc2626;
          border-radius: 9999px;
        }
        .kartu__gambar { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; border-radius: 8px; }
    
        /* Kalau wadahnya lebar ≥ 420px, kartu berubah jadi horizontal */
        @container (min-width: 420px) {
          .kartu { display: grid; grid-template-columns: 160px 1fr; gap: 1rem; align-items: center; }
        }
    
        /* Tombol chat melayang di pojok layar */
        .fab {
          position: fixed;
          right: 1rem;
          bottom: 1rem;
          z-index: var(--z-fab);
          padding: 0.9rem 1.2rem;
          color: white;
          background: #16a34a;
          border: none;
          border-radius: 9999px;
        }
      </style>
    </head>
    <body>
      <header class="header"><div class="kontainer"><strong>Kopi Senja</strong></div></header>
    
      <main class="kontainer">
        <h1 class="judul">Kopi lokal, diseduh hari ini</h1>
        <div class="layout">
          <section class="produk">
            <article class="kartu">
              <span class="kartu__badge">-20%</span>
              <img class="kartu__gambar" src="https://images.unsplash.com/photo-1461023058943-07fcbe16d735?w=600"
                   alt="Es kopi susu dalam gelas plastik" width="600" height="450">
              <div><h2>Es Kopi Susu</h2><p>Rp18.000</p></div>
            </article>
          </section>
          <aside class="produk">
            <article class="kartu">
              <img class="kartu__gambar" src="https://images.unsplash.com/photo-1514432324607-a09d9b4aefdd?w=600"
                   alt="Kopi hitam panas dalam cangkir" width="600" height="450">
              <div><h2>Kopi Tubruk</h2><p>Rp10.000</p></div>
            </article>
          </aside>
        </div>
        <p style="height: 150vh">Scroll ke bawah untuk melihat header tetap menempel.</p>
      </main>
    
      <button class="fab" type="button">Chat kami</button>
    </body>
    </html>
    <!-- → HP: satu kolom, kedua kartu vertikal (gambar di atas teks).
         Tablet/desktop: dua kolom. Kartu di kolom utama yang lebar berubah horizontal (container query),
         sedangkan kartu di sidebar sempit tetap vertikal, padahal class-nya sama.
         Header menempel saat scroll, tombol "Chat kami" melayang di pojok kanan bawah. -->
    

    Uji di DevTools dengan Device Toolbar (Ctrl+Shift+M atau Cmd+Shift+M) lalu tarik lebar layar.

    Kesalahan umum

    1. Anak absolute lari ke pojok halaman. Induknya lupa diberi position: relative. Cara benar: pasang relative di elemen yang ingin dijadikan patokan.
    2. sticky tidak menempel. Biasanya lupa top: 0, atau leluhur punya overflow: hidden. Cara benar: tambahkan ambang dan periksa overflow leluhur.
    3. Perang z-index (999, 9999, 99999). Masalah sebenarnya sering stacking context. Cara benar: pakai skala variabel, cek apakah induk membuat stacking context (transform, opacity, dll).
    4. Desktop-first dengan banyak max-width yang saling timpa. Cara benar: mulai dari mobile, tambah dengan min-width.
    5. font-size: 5vw murni. Teks jadi terlalu kecil di HP, terlalu besar di monitor lebar, dan tidak ikut zoom pengguna. Cara benar: clamp() dengan komponen rem.

    Latihan

    1. Mudah, Buat kartu dengan badge "Baru" di pojok kiri atas memakai pasangan relative dan absolute.
    2. Sedang, Buat daftar menu yang mobile-first: satu kolom di HP, dua kolom mulai 40rem, empat kolom mulai 64rem.
    3. Menantang, Buat komponen .profil yang vertikal (foto di atas, teks di bawah) saat wadahnya di bawah 350px dan horizontal saat 350px ke atas, memakai container query. Ukuran nama memakai clamp() berbasis unit cqi.
    Lihat solusi
    /* Soal 1 */
    .kartu-baru { position: relative; padding: 1.5rem; border: 1px solid #ddd; }
    .kartu-baru__badge {
      position: absolute;
      top: 0;
      left: 0;
      padding: 0.2rem 0.6rem;
      color: white;
      background: #2563eb;
      border-bottom-right-radius: 8px;
    }
    
    /* Soal 2 */
    .menu-grid { display: grid; gap: 1rem; }
    @media (min-width: 40rem) { .menu-grid { grid-template-columns: repeat(2, 1fr); } }
    @media (min-width: 64rem) { .menu-grid { grid-template-columns: repeat(4, 1fr); } }
    
    /* Soal 3 */
    .wadah-profil { container-type: inline-size; }
    .profil { display: flex; flex-direction: column; gap: 1rem; align-items: center; text-align: center; }
    .profil__foto { width: 96px; height: 96px; border-radius: 50%; object-fit: cover; }
    .profil__nama { font-size: clamp(1.1rem, 4cqi + 0.5rem, 2rem); }
    
    @container (min-width: 350px) {
      .profil { flex-direction: row; text-align: left; }
    }
    /* → Di wadah 300px: foto di atas nama. Di wadah 500px: foto di kiri, nama di kanan dan lebih besar. */
    

    Ringkasan

    Kuis pemahaman

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