Cincin Olimpiade CSS

Beberapa tahun yang lalu saat Olimpiade Tokyo 2020 saya membuat demo cincin Olimpiade animasi 3D. Saya menyukainya, tampak hebat, dan saya menyukai efek cincin yang saling bersilangan.

Opsi penyertaan CodePen alternatif

Namun kodenya sendiri sudah agak ketinggalan jaman. Saya menulisnya di SCSS, dan saya tidak melakukannya dengan baik. Saya tahu ini bisa lebih baik, setidaknya menurut standar modern.

-Advertisement-.


Jadi, saya memutuskan untuk membuat versi beta dari awal lagi untuk menghormati Olimpiade tahun ini. Saya menulis CSS dasar kali ini, memanfaatkan fitur modern seperti fungsi trigonometri untuk mengurangi jumlah angka ajaib dan aturan warna relatif untuk meningkatkan manajemen warna. Yang menarik, ternyata versi beta baru ini jauh lebih efisien dengan baris kode yang lebih sedikit dibandingkan versi SCSS lama yang saya tulis pada tahun 2020!

Lihatlah kembali tab CSS di demo pertama karena kita akan mendapatkan sesuatu yang benar-benar berbeda — dan lebih baik — dengan pendekatan yang akan kita gunakan bersama. Jadi, mari kita mulai!

Pengkodean

Kami akan menggunakan lapisan untuk membuat efek 3D. Lapisan-lapisan ini ditempatkan satu per satu (pada sumbu z) untuk mendapatkan kedalaman objek 3D, yang dalam kasus kita adalah sebuah cincin. Kombinasi bentuk, ukuran, dan warna setiap lapisan — serta perbedaannya dari satu lapisan ke lapisan lainnya — inilah yang menciptakan keseluruhan objek 3D.

Dalam hal ini, saya menggunakan 16 lapisan di mana setiap lapisan memiliki warna yang berbeda (dengan lapisan yang lebih gelap ditumpuk di belakang) untuk sedikit efek pencahayaan, dan menggunakan ukuran dan ketebalan setiap lapisan untuk membuat bentuk lingkaran bulat.

Untuk HTML, kita membutuhkan lima <div> Elemen, satu untuk setiap episode, di mana masing-masing <div> Ia memiliki 16 elemen yang bertindak sebagai lapisan, yang saya lapisi <i> Tag. Kelima cincin itu akan kita masukkan ke dalam wadah utama untuk mengikat semuanya. Kami akan memberikan wadah utama .rings kelas dan setiap episode, secara kreatif, .ring musim.

Ini adalah versi singkat HTML yang menunjukkan cara mengompilasinya:


<div class="rings">
  <div class="ring">
    <i style="--i: 1;"></i>
    <i style="--i: 2;"></i>
    <i style="--i: 3;"></i>
    <i style="--i: 4;"></i>
    <i style="--i: 5;"></i>
    <i style="--i: 6;"></i>
    <i style="--i: 7;"></i>
    <i style="--i: 8;"></i>
    <i style="--i: 9;"></i>
    <i style="--i: 10;"></i>
    <i style="--i: 11;"></i>
    <i style="--i: 12;"></i>
    <i style="--i: 13;"></i>
    <i style="--i: 14;"></i>
    <i style="--i: 15;"></i>
    <i style="--i: 16;"></i>
  </div>

  <!-- 4 more rings... -->  

</div>

Melihat --i Properti khusus yang Anda kunjungi style Karakteristik masing-masing <i> komponen:

<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<!-- etc. -->

Kami akan menggunakan --i Menghitung posisi, ukuran, dan warna setiap lapisan. Itu sebabnya saya menetapkan nilainya sebagai bilangan bulat dalam urutan menaik — ini akan menjadi kelipatan dari urutan dan desain setiap lapisan.

Kiat Pro: Anda dapat menghindari penulisan HTML secara manual untuk setiap lapisan jika Anda bekerja pada IDE yang mendukung Emmet. Namun jika belum, jangan khawatir, karena CodePen mendukungnya! Masukkan yang berikut ini ke dalam editor HTML Anda dan tekan Tab Tekan tombol pada keyboard Anda untuk memperluasnya menjadi 16 lapisan: i*16[style="--i: $;"]

CSS (vanila)

Mari kita mulai dengan orang tua .rings Untuk saat ini, container hanya akan mendapat posisi relatif. Tanpa menentukan posisi relatif, perulangan akan dihapus dari aliran dokumen dan berakhir di luar halaman di suatu tempat ketika posisi absolut ditetapkan pada perulangan tersebut.

.rings {
  position: relative;
}

.ring {
  position: absolute;
}

Mari kita lakukan hal yang sama dengan <i> elemen, tetapi gunakan sarang CSS untuk menjaga kode tetap kompak. Kami akan menambahkan border-radius Sedangkan bagian tepi persegi kita potong hingga membentuk lingkaran sempurna.

.rings {
  position: relative;
}

.ring {
  position: absolute;
  
  i {
    position: absolute;
    border-radius: 50%;
  }
}

Bagian terakhir dari desain dasar yang akan kita terapkan sebelum melanjutkan ke berikutnya adalah properti kustom untuk --ringColorIni akan membuat pewarnaan loop cukup mudah karena kita dapat menulisnya satu kali, dan kemudian mengulanginya lapis demi lapis. Kami mengumumkan --ringColor pada border Properti ini karena kita hanya ingin mewarnai tepi luar setiap lapisan, bukan mengisinya sepenuhnya background-color:

.rings {
  position: relative;
}

.ring {
  position: absolute;
  --ringColor: #0085c7;
  
  i {
    position: absolute;
    inset: -100px;
    border: 16px var(--ringColor) solid;
    border-radius: 50%;
  }
}

Apakah kamu memperhatikan aku menyelipkan sesuatu yang lain ke sana? Ini benar, inset Properti juga ada dan disetel ke nilai negatif 100pxIni mungkin terdengar agak aneh, jadi mari kita bicarakan hal itu terlebih dahulu selagi kita terus merancang karya kita.

Inklusi negatif

Tetapkan nilai negatif ke inset Properti berarti posisi lapisan tersebut berada <>di luar itu .ring Barang. Jadi, kita mungkin menganggapnya sebagai “permulaan”. Dalam kasus kami, .ring Itu tidak memiliki ukuran karena tidak ada konten atau properti CSS untuk memberikan dimensi. Artinya kelas itu inset (atau lebih tepatnya “permulaan”) adalah 100px Ke segala arah, menuju ke .ring Ini adalah 200×200 piksel.

Kotak transparan dengan batas biru digambar di atas garis grafik dengan panah di dalamnya yang menunjukkan offset lapisan cincin dan pengaruhnya terhadap ukuran elemen cincin.

Mari kita periksa apa yang kita miliki sejauh ini:

Opsi penyertaan CodePen alternatif

Posisi untuk kedalaman

Kami menggunakan lapisan untuk menciptakan kesan mendalam. Kita melakukan ini dengan menempatkan masing-masing dari enam belas lapisan di sepanjang sumbu z, yang menumpuk elemen dari depan ke belakang. Kami akan memberi jarak antara setiap lapisan hanya 1 cm. 2px Pisahkan – Ini adalah satu-satunya ruang yang kita perlukan untuk membuat sedikit pemisahan visual antara setiap lapisan, memberi kita kedalaman yang kita cari.

untuk diingat --i Properti khusus yang kami gunakan dalam HTML?

<i style="--i: 1;"></i>
<i style="--i: 2;"></i>
<i style="--i: 3;"></i>
<!-- etc. -->

Sekali lagi, ini adalah pengganda yang membantu kita translate Setiap lapisan berada di sepanjang sumbu z. Mari buat properti kustom baru yang mendefinisikan persamaan sehingga kita dapat menerapkannya ke setiap lapisan:

i {
  --translateZ: calc(var(--i) * 2px);
}

Untuk apa kita menerapkan ini? Kita bisa menggunakan CSS transform Milik. Dengan cara ini, kita dapat memutar layer secara vertikal (yaitu, rotateY()) sambil menerjemahkannya sepanjang sumbu z:

i {
  --translateZ: calc(var(--i) * 2px);

  transform: rotateY(-45deg) translateZ(var(--translateZ));
}

Warna untuk penetasan

Untuk membuat bayangan warna, kita akan menggelapkan lapisan tergantung pada posisinya sehingga lapisan menjadi lebih gelap saat kita berpindah dari depan sumbu z ke belakang. Ada beberapa cara untuk melakukan ini. Salah satunya adalah dengan menghilangkan lapisan hitam lainnya sambil mengurangi opacity. Cara lain adalah dengan mengatur saluran “Kecerahan”. hsl() Fungsi warna yang nilainya “lebih terang” di latar depan dan semakin gelap di belakang. Pilihan ketiga adalah bermain-main dengan opacity layer, tapi ini menjadi berantakan.

Meskipun kami menggunakan ketiga metode ini, menurut saya sintaks warna relatif CSS modern adalah cara terbaik. Kami telah menentukan mode warna default --ringColor Properti khusus. Kita dapat memasukkannya melalui sintaks warna relatif untuk memanipulasinya dengan warna lain untuk setiap cincin <i> lapisan.

Pertama, kita memerlukan properti khusus baru yang dapat kita gunakan untuk menghitung nilai “ringan”:

.ring {
  --ringColor: #0085c7;
  
  i {
    --light: calc(var(--i) / 16);

    border: 16px var(--ringColor) solid;
  }
}

Kami akan menggunakan calc()-Hasil yang diberikan di properti khusus lainnya menetapkan nilai default kami --ringColor Melalui sintaks warna relatif dimana --light Fitur custom membantu mengatur kecerahan warna yang dihasilkan.

.ring {
  --ringColor: #0085c7;
  
  i {
    --light: calc(var(--i) / 16);
    --layerColor: rgb(from var(--ringColor) calc(r * var(--light)) calc(g * var(--light)) calc(b * var(--light)));

    border: 16px var(--ringColor) solid;
  }
}

Itu persamaan yang sangat rumit! Namun tampaknya rumit karena rumus warna relatif memerlukan argumen untuk setiap saluran dalam warna (RGB) dan kami menghitung masing-masing saluran tersebut.

rgb(from origin-color channelR channelG channelB)

Untuk perhitungannya, kami mengalikan setiap saluran RGB dengan --light Properti yang ditetapkan, yaitu angka di antaranya 0 Dan 1 divided Tergantung pada jumlah lapisan, 16.

Saatnya melakukan pemeriksaan lagi untuk melihat posisi kita:

Opsi penyertaan CodePen alternatif

Buat bentuknya

Untuk mendapatkan bentuk cincin melingkar, kita akan mengatur ukuran lapisan (yaitu ketebalan) menggunakan border Di sinilah kita bisa mulai menggunakan trigonometri dalam pekerjaan kita!

Kami ingin ketebalan setiap cincin berada di antara nilai 0deg ke 180deg – Karena kita sebenarnya hanya membuat setengah lingkaran, – jadi kita akan membaginya 180deg Tergantung pada jumlah lapisan, 16yang keluar ke 11.25deg. menggunakan sin() Fungsi trigonometri (yang setara dengan <>balik Dan <>Sisi miring sudut dalam Sisi sudut kanan, kita mendapatkan ekspresi ini untuk layer --size:

--size: calc(sin(var(--i) * 11.25deg) * 16px);

Jadi, terserah --i Ditemukan dalam HTML, ini bertindak sebagai pengganda untuk penghitungan lapisan border Ikan. Kami telah mendeklarasikan batas kelas sebagai berikut:

i {
  border: 16px var(--ringColor) solid;
)

Sekarang kita dapat mengganti kode yang diprogram 16px nilai dengan --size akun:

i {
  --size: calc(sin(var(--i) * 11.25deg) * 16px);

  border: var(--size) var(--layerColor) solid;
)

Tetapi! Seperti yang Anda perhatikan, kami tidak mengubah ukuran layer saat kami mengubah ukurannya. border tawaran itu. Akibatnya, bentuk lingkaran hanya muncul di sisi dalam lapisan saja. Kuncinya di sini adalah memahami penyetelan itu --size dengan inset Properti berarti tidak mempengaruhi elemen box-sizingHasilnya adalah cincin 3D yang jelas, namun sebagian besar bayangannya terkubur.

⚠️ Memutar media secara otomatis

Kita dapat menampilkan shader dengan perhitungan baru inset Untuk setiap lapisan. Inilah yang saya lakukan di versi 2020, tetapi saya rasa saya menemukan cara yang lebih mudah: tambahkan outline Dengan hal yang sama border Nilai yang dibutuhkan untuk menyelesaikan busur ada di sisi luar ring.

i {
  --size: calc(sin(var(--i) * 11.25deg) * 16px);

  border: var(--size) var(--layerColor) solid;
  outline: var(--size) var(--layerColor) solid;
}

Kami memiliki lingkaran yang tampak lebih alami setelah kami buat outline:

Opsi penyertaan CodePen alternatif

Pindahkan cincinnya

Saya harus memindahkan cincin di demo terakhir untuk membandingkan bayangan cincin sebelum dan sesudahnya. Kami akan menggunakan animasi yang sama di demo final, jadi mari tunjukkan bagaimana Anda melakukannya sebelum menambahkan empat episode lainnya ke HTML

Saya tidak mencoba melakukan sesuatu yang mewah; Saya hanya mengatur rotasi pada sumbu y -45deg ke 45deg (itu translateZ (Nilainya tetap konstan).

@keyframes ring {
  from { transform: rotateY(-45deg) translateZ(var(--translateZ, 0)); }
  to { transform: rotateY(45deg) translateZ(var(--translateZ, 0)); }
}

Adapun animation Properti, saya telah memberikan namanya ring dan durasi tertentu (setidaknya untuk saat ini) 3syang diulangi tanpa henti. Sesuaikan fungsi pengaturan waktu animasi menggunakan ease-in-out Dan alternatemasing-masing, memberi kita gerakan maju mundur yang mulus.

i {
  animation: ring 3s infinite ease-in-out alternate;
}

Beginilah cara kerja animasi!

Tambahkan lebih banyak episode

Sekarang kita dapat menambahkan empat loop tersisa ke HTML. Ingat kami memiliki total lima episode dan setiap episode memiliki 16 <i> Lapisan. Ini mungkin terlihat sederhana seperti ini:

<div class="rings">
  <div class="ring"> <!-- <i> layers --> </div>
  <div class="ring"> <!-- <i> layers --> </div>
  <div class="ring"> <!-- <i> layers --> </div>
  <div class="ring"> <!-- <i> layers --> </div>
  <div class="ring"> <!-- <i> layers --> </div>
</div>

Ada sesuatu yang elegan dalam kesederhanaan tanda ini. Dan kita bisa menggunakan CSS nth-child() Pemilih semu untuk mengidentifikasi mereka satu per satu. Saya ingin lebih jelas dari itu dan saya akan mencobanya masing-masing .ring Dan kelas tambahan yang bisa kita gunakan untuk mendefinisikan loop tertentu secara eksplisit.

<div class="rings">
  <div class="ring ring__1"> <!-- layers --> </div>
  <div class="ring ring__2"> <!-- layers --> </div>
  <div class="ring ring__3"> <!-- layers --> </div>
  <div class="ring ring__4"> <!-- layers --> </div>
  <div class="ring ring__5"> <!-- layers --> </div>
</div>

Tugas kita sekarang adalah menyesuaikan setiap episode satu per satu. Saat ini, semuanya tampak seperti episode pertama yang kami buat bersama. Kita akan menggunakan kelas unik yang baru saja kita atur di HTML untuk memberikan warna, posisi, dan durasi animasinya sendiri.

Kabar baiknya adalah kami telah menggunakan fitur khusus selama ini! Yang harus kita lakukan adalah memperbarui nilai di kelas setiap loop unik.

.ring {
  &.ring__1 { --ringColor: #0081c8; --duration: 3.2s; --translate: -240px, -40px; }
  &.ring__2 { --ringColor: #fcb131; --duration: 2.6s; --translate: -120px, 40px; }
  &.ring__3 { --ringColor: #444444; --duration: 3.0s; --translate: 0, -40px; }
  &.ring__4 { --ringColor: #00a651; --duration: 3.4s; --translate: 120px, 40px; }
  &.ring__5 { --ringColor: #ee334e; --duration: 2.8s; --translate: 240px, -40px; }
}

Jika Anda bertanya-tanya di mana itu --ringColor Nilai-nilai tersebut berasal dari warna yang didokumentasikan oleh Komite Olimpiade Internasional. --duration Mereka sedikit diimbangi satu sama lain untuk mengganggu pergerakan antara cincin dan cincin --translate'Dr 120px Pisahkan lalu bersarang secara vertikal dengan bergantian posisinya 40px Dan -40px.

Mari kita terapkan materi terjemahannya .ring Elemen:

.ring {
  transform: translate(var(--translate));
}

Sebelumnya, kami mengatur durasi animasi menjadi tiga detik yang telah diprogram sebelumnya:

i {
  animation: ring 3s infinite ease-in-out alternate;
}

Ini saat yang tepat untuk menggantinya dengan fitur khusus yang menghitung durasi setiap episode satu per satu.

i {
  animation: ring var(--duration) -10s infinite ease-in-out alternate;
}

Wah, wah! Ada apa? -10s Berapakah nilai dari tindakan ini? Meskipun setiap lapisan loop diatur untuk dianimasikan dengan durasi berbeda, sudut awal animasinya sama. Menambahkan penundaan negatif tetap saat mengubah durasi akan memastikan bahwa setiap putaran animasi dimulai pada sudut yang berbeda.

Sekarang kita memiliki sesuatu yang ada <>hampir tidak saya menyelesaikan:

Opsi penyertaan CodePen alternatif

Beberapa sentuhan akhir

Kami telah mencapai tahap akhir! Animasinya tampak bagus, tetapi saya ingin menambahkan beberapa hal lagi. Yang pertama adalah gambar kecil-10deg “Miringkan” pada sumbu x elemen aslinya .rings Wadah. Ini akan membuat kita seolah-olah melihat sesuatu dari sudut pandang yang lebih tinggi.

.rings {
  rotate: x -10deg;
}

Sentuhan terakhir kedua menyangkut bayangan. Kami benar-benar dapat mendefinisikan kedalaman 3D pada pekerjaan kami dan yang diperlukan hanyalah sebuah definisi .ring Elemen ::after Elemen palsu dan desainnya <>Dia mencintai Bayangan.

Pertama, kita akan mengatur lebar batas dummy dan outline ke nilai tetap (24px) sambil mengatur warnanya menjadi hitam semi transparan (#0003). Maka kita akan melakukannya translate Jadi mereka tampak semakin jauh. Kami juga akan melakukannya inset Kami mengaturnya agar cocok dengan episode sebenarnya. Pada dasarnya, kami memindahkan elemen semu relatif terhadap elemen sebenarnya.

.ring {
  /* etc. */

  &::after {
    content: '';
    position: absolute;
    inset: -100px;
    border: 24px #0003 solid;
    outline: 24px #0003 solid;
    translate: 0 -100px -400px;
  }
}

Gambar palsu tersebut tampaknya tidak begitu misterius saat ini. Tapi nanti akan terlihat seperti itu blur() mereka sedikit:

.ring {
  /* etc. */

  &::after {
    content: '';
    position: absolute;
    inset: -100px;
    border: 24px #0003 solid;
    outline: 24px #0003 solid;
    translate: 0 -100px -400px;
    filter: blur(12px);
  }
}

Bayangannya juga berbentuk agak persegi. Mari kita pastikan bentuknya bulat seperti cincin:

.ring {
  /* etc. */

  &::after {
    content: '';
    position: absolute;
    inset: -100px;
    border: 24px #0003 solid;
    outline: 24px #0003 solid;
    translate: 0 -100px -400px;
    filter: blur(12px);
    border-radius: 50%;
  }
}

Oh, dan kita harus memasang animasi yang sama pada yang palsu sehingga bayangan bergerak bersamaan dengan cincin:

.ring {
  /* etc. */

  &::after {
    content: '';
    position: absolute;
    inset: -100px;
    border: 24px #0003 solid;
    outline: 24px #0003 solid;
    translate: 0 -100px -400px;
    filter: blur(12px);
    border-radius: 50%;
    animation: ring var(--duration) -10s infinite ease-in-out alternate;
  }
}

Demo terakhir

Mari kita berhenti dan mengagumi hasil karya kita:

Opsi penyertaan CodePen alternatif

Pada akhirnya, saya sangat senang dengan cincin Olimpiade versi 2024. Versi 2020 menyelesaikan pekerjaannya dan mungkin merupakan pendekatan yang tepat pada saat itu. Namun dengan semua fitur yang kita dapatkan di CSS modern saat ini, saya memiliki banyak peluang untuk meningkatkan kode sehingga tidak hanya lebih efisien, namun lebih dapat digunakan kembali – misalnya, ini dapat digunakan di proyek lain dan “disesuaikan” hanya dengan memperbarui --ringColor Properti yang dialokasikan.

Pada akhirnya, latihan ini menunjukkan kepada saya kekuatan dan fleksibilitas CSS modern. Kami mengambil ide yang sudah ada dengan kompleksitas dan menciptakannya kembali dengan kesederhanaan dan keanggunan.


Cincin Olimpiade CSS awalnya diterbitkan di CSS-Tricks, bagian dari keluarga DigitalOcean. Anda harus mendapatkan buletin.

Sumber

-Advertisement-.

IDJ