HTML5 SVG Animasyonu CSS3 ve Vanilla JavaScript ile Doldurun

Yayınlanan: 2022-03-10
Hızlı özet ↬ Bu makalede, Awwwards web sitesinden animasyonlu not ekranının nasıl oluşturulacağını öğrenebilirsiniz. HTML5 SVG daire öğesini, onun kontur özelliklerini ve bunların CSS değişkenleri ve Vanilla JavaScript ile nasıl canlandırılacağını tartışır.

SVG, Ölçeklenebilir Vektör Grafikleri anlamına gelir ve vektör grafikleri için standart bir XML tabanlı biçimlendirme dilidir. 2B düzlemde bir dizi nokta belirleyerek yollar, eğriler ve şekiller çizmenize olanak tanır. Ayrıca, animasyonlar oluşturmak için bu yollara (kontur, renk, kalınlık, dolgu ve daha fazlası gibi) seğirme özellikleri ekleyebilirsiniz.

Nisan 2017'den bu yana, CSS Düzey 3 Dolgu ve Kontur Modülü, SVG renklerinin ve dolgu desenlerinin her bir öğede nitelikler ayarlamak yerine harici bir stil sayfasından ayarlanmasına izin verir. Bu öğreticide basit bir düz altıgen renk kullanacağız, ancak hem dolgu hem de kontur özellikleri aynı zamanda desenleri, degradeleri ve görüntüleri değer olarak kabul eder.

Not : Awwwards web sitesini ziyaret ederken, animasyonlu not ekranı yalnızca tarayıcı genişliği 1024 piksel veya daha fazlasına ayarlanmış olarak görüntülenebilir.

Not Ekran Projesi Demosu
Nihai sonucun bir demosu (Büyük önizleme)
  • Demo: Not Ekran Projesi
  • Repo: Not Ekranı Repo
Atlamadan sonra daha fazlası! Aşağıdan okumaya devam edin ↓

Dosya Yapısı

Dosyaları terminalde oluşturarak başlayalım:

 mkdir note-display cd note-display touch index.html styles.css scripts.js

HTML

İşte hem css hem de js dosyalarını birbirine bağlayan ilk şablon:

 <html lang="en"> <head> <meta charset="UTF-8"> <title>Note Display</title> <link rel="stylesheet" href="./styles.css"> </head> <body> <script src="./scripts.js"></script> </body> </html>

Her not öğesi bir liste öğesinden oluşur: circle , note değerini ve label tutan li .

Liste öğesi öğesi ve doğrudan alt öğeler
Liste öğesi öğesi ve doğrudan alt öğeleri: .circle , .percent ve .label . (Büyük önizleme)

.circle_svg , iki <circle> öğesini saran bir SVG öğesidir. Birincisi doldurulacak yol, ikincisi ise canlandırılacak dolgu.

SVG öğeleri
SVG öğeleri. SVG sarmalayıcı ve daire etiketleri. (Büyük önizleme)

note , tamsayı ve ondalık sayılara ayrılmıştır, böylece onlara farklı yazı tipi boyutları uygulanabilir. label basit bir <span> . Yani, tüm bunları bir araya getirmek şöyle görünür:

 <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li>

cx ve cy nitelikleri dairenin x eksenini ve y ekseni merkez noktasını tanımlar. r özelliği yarıçapını tanımlar.

Muhtemelen sınıf isimlerinde alt çizgi/tire desenini fark etmişsinizdir. Bu, block , element ve modifier anlamına gelen BEM'dir. Öğe adlandırmanızı daha yapılandırılmış, organize ve anlamsal hale getiren bir metodolojidir.

Önerilen okumalar : BEM'e İlişkin Bir Açıklama ve Neden İhtiyaç Duyduğunuz

Şablon yapılarını bitirmek için, dört liste öğesini sırasız bir liste öğesine saralım:

Sırasız liste sarmalayıcı
Sırasız liste sarmalayıcısı dört li çocuğu tutar (Büyük önizleme)
 <ul class="display-container"> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Reasonable</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Usable</span> </li> <li class="note-display"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Exemplary</span> </li> </ul>

Transparent , Reasonable , Usable ve Exemplary etiketlerinin ne anlama geldiğini kendinize sormalısınız. Programlama hakkında ne kadar bilgi sahibi olursanız, kod yazmanın sadece uygulamayı işlevsel hale getirmekle ilgili olmadığını, aynı zamanda uzun vadeli sürdürülebilir ve ölçeklenebilir olmasını sağlamakla ilgili olduğunu anlayacaksınız. Bu, yalnızca kodunuzun değiştirilmesi kolaysa elde edilir.

" TRUE kısaltması, yazdığınız kodun gelecekte değişikliğe uyum sağlayıp sağlayamayacağına karar vermenize yardımcı olmalıdır."

Öyleyse, bir dahaki sefere kendinize şunu sorun:

  • Transparent : Kod değişikliklerinin sonuçları açık mı?
  • Reasonable : Maliyet-fayda buna değer mi?
  • Usable : Beklenmedik senaryolarda tekrar kullanabilecek miyim?
  • Exemplary : Gelecekteki kod için örnek olarak yüksek kalite sunuyor mu?

Not : Sandi Metz'in yazdığı “Pratik Nesneye Yönelik Tasarım Ruby”, TRUE diğer ilkelerle birlikte açıklar ve bunların tasarım desenleriyle nasıl elde edileceğini açıklar. Henüz tasarım kalıplarını incelemek için biraz zaman ayırmadıysanız, bu kitabı yatmadan önce okuduğunuz kitaplara eklemeyi düşünün.

CSS

Yazı tiplerini içe aktaralım ve tüm öğelere sıfırlama uygulayalım:

 @import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200'); * { padding: 0; margin: 0; box-sizing: border-box; }

box-sizing: border-box özelliği, bir öğenin toplam genişliğine ve yüksekliğine dolgu ve sınır değerleri ekler, bu nedenle boyutlarını hesaplamak daha kolaydır.

Not : box-sizing hakkında görsel bir açıklama için lütfen “CSS Kutu Boyutlandırma ile Hayatınızı Kolaylaştırın” bölümünü okuyun.

 body { height: 100vh; color: #fff; display: flex; background: #3E423A; font-family: 'Nixie One', cursive; } .display-container { margin: auto; display: flex; }

Rules display: flex in body ve margin-auto öğelerini .display-container içinde birleştirerek, alt öğeyi hem dikey hem de yatay olarak ortalamak mümkündür. .display-container öğesi ayrıca bir flex-container olacaktır; bu şekilde, çocukları ana eksen boyunca aynı sıraya yerleştirilecektir.

.note-display liste öğesi ayrıca bir flex-container olacaktır. Merkezleme için çok sayıda çocuk olduğundan, bunu justify-content ve align-items özellikleri aracılığıyla yapalım. Tüm flex-items , cross ve main eksen boyunca ortalanacaktır. Bunların ne olduğundan emin değilseniz, "CSS Flexbox Temelleri Görsel Kılavuzu"ndaki hizalama bölümüne bakın.

 .note-display { display: flex; flex-direction: column; align-items: center; margin: 0 25px; }

Kontur canlı uçlarını tamamen şekillendiren kontur stroke-width , stroke-opacity ve stroke-linecap ayarlayarak dairelere bir kontur uygulayalım. Ardından, her daireye bir renk ekleyelim:

 .circle__progress { fill: none; stroke-width: 3; stroke-opacity: 0.3; stroke-linecap: round; } .note-display:nth-child(1) .circle__progress { stroke: #AAFF00; } .note-display:nth-child(2) .circle__progress { stroke: #FF00AA; } .note-display:nth-child(3) .circle__progress { stroke: #AA00FF; } .note-display:nth-child(4) .circle__progress { stroke: #00AAFF; }

percent elemanını mutlak olarak konumlandırmak için, neyin ne olduğunu kesinlikle bilmek gerekir. .circle öğesi referans olmalıdır, bu yüzden ona position: relative ekleyelim.

Not : Mutlak konumlandırma hakkında daha derin ve görsel bir açıklama için lütfen “Bir Kez ve Herkes İçin CSS Pozisyonu Mutlak Nasıl Anlaşılır” bölümünü okuyun.

Öğeleri ortalamanın başka bir yolu top: 50% , left: 50% ve transform: translate(-50%, -50%); öğenin merkezini ebeveyninin merkezine konumlandıran.

 .circle { position: relative; } .percent { width: 100%; top: 50%; left: 50%; position: absolute; font-weight: bold; text-align: center; line-height: 28px; transform: translate(-50%, -50%); } .percent__int { font-size: 28px; } .percent__dec { font-size: 12px; } .label { font-family: 'Raleway', serif; font-size: 14px; text-transform: uppercase; margin-top: 15px; }

Şimdiye kadar, şablon şöyle görünmelidir:

Tamamlanmış ilk şablon
Tamamlanmış şablon öğeleri ve stilleri (Geniş önizleme)

Dolgu Geçişi

Daire animasyonu, iki daire SVG özelliğinin yardımıyla oluşturulabilir: stroke-dasharray ve stroke-dashoffset .

" stroke-dasharray , bir konturdaki tire-boşluk düzenini tanımlar."

En fazla dört değer alabilir:

  • Yalnızca bir tamsayıya ( stroke-dasharray: 10 ) ayarlandığında, tireler ve boşluklar aynı boyuta sahiptir;
  • İki değer için ( stroke-dasharray: 10 5 ), ilki tirelere, ikincisi boşluklara uygulanır;
  • Üçüncü ve dördüncü formlar ( stroke-dasharray: 10 5 2 ve stroke-dasharray: 10 5 2 3 ) çeşitli boyutlarda tireler ve boşluklar oluşturacaktır.
Kontur dasharray özellik değerleri
stroke-dasharray özellik değerleri (Büyük önizleme)

Soldaki resim, daire çevre uzunluğu olan 0 ile 238 piksel arasında ayarlanan stroke-dasharray özelliğini gösterir.

İkinci görüntü, tire dizisinin başlangıcını stroke-dashoffset özelliğini temsil eder. Ayrıca 0'dan daire çevre uzunluğuna ayarlanır.

Kontur dasharray ve dashoffset özellikleri
stroke-dasharray ve vuruş-dashoffset özellikler (Büyük önizleme)

Doldurma efektini oluşturmak için, tüm uzunluğu büyük bir tire ile doldurulacak ve boşluk kalmayacak şekilde stroke-dasharray çevre uzunluğuna ayarlayacağız. Onu da aynı değerle dengeleyeceğiz, böylece “gizlenecek”. Daha sonra stroke-dashoffset , geçiş süresine göre vuruşu doldurarak karşılık gelen nota değerine güncellenecektir.

Özelliklerin güncellenmesi betiklerde CSS Değişkenleri aracılığıyla yapılacaktır. Değişkenleri tanımlayalım ve özellikleri ayarlayalım:

 .circle__progress--fill { --initialStroke: 0; --transitionDuration: 0; stroke-opacity: 1; stroke-dasharray: var(--initialStroke); stroke-dashoffset: var(--initialStroke); transition: stroke-dashoffset var(--transitionDuration) ease; }

Başlangıç ​​değerini ayarlamak ve değişkenleri güncellemek için, document.querySelectorAll ile tüm .note-display öğelerini seçerek başlayalım. transitionDuration 900 milisaniyeye ayarlanacaktır.

Ardından, display dizisini yineliyoruz, onun .circle__progress.circle__progress--fill ve çevre uzunluğunu hesaplamak için HTML'de ayarlanan r niteliğini çıkartıyoruz. Bununla, ilk --dasharray ve --dashoffset değerlerini ayarlayabiliriz.

Animasyon, --dashoffset değişkeni 100ms setTimeout tarafından güncellendiğinde ortaya çıkar:

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { let progress = display.querySelector('.circle__progress--fill'); let radius = progress.r.baseVal.value; let circumference = 2 * Math.PI * radius; progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); progress.style.setProperty('--initialStroke', circumference); setTimeout(() => progress.style.strokeDashoffset = 50, 100); });

Üstten başlayarak geçişi elde etmek için .circle__svg öğesinin döndürülmesi gerekir:

 .circle__svg { transform: rotate(-90deg); } 
Kontur özellikleri geçişi
Kontur özellikleri geçişi (Büyük önizleme)

Şimdi, notaya göre dashoffset değerini hesaplayalım. Not değeri, data-* özelliği aracılığıyla her bir li öğesine eklenecektir. * , ihtiyaçlarınıza uyan herhangi bir ad için değiştirilebilir ve ardından öğenin veri kümesi aracılığıyla JavaScript'te alınabilir: element.dataset.* .

Not : data-* özniteliği hakkında daha fazla bilgiyi MDN Web Dokümanlarında okuyabilirsiniz.

Niteliğimiz “ data-note ” olarak adlandırılacaktır:

 <ul class="display-container"> + <li class="note-display" data-note="7.50"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Transparent</span> </li> + <li class="note-display" data-note="9.27"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Reasonable</span> </li> + <li class="note-display" data-note="6.93"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Usable</span> </li> + <li class="note-display" data-note="8.72"> <div class="circle"> <svg width="84" height="84" class="circle__svg"> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--path"></circle> <circle cx="41" cy="41" r="38" class="circle__progress circle__progress--fill"></circle> </svg> <div class="percent"> <span class="percent__int">0.</span> <span class="percent__dec">00</span> </div> </div> <span class="label">Exemplary</span> </li> </ul>

parseFloat yöntemi, display.dataset.note tarafından döndürülen dizeyi bir kayan noktalı sayıya dönüştürür. offset , maksimum puana ulaşmak için eksik olan yüzdeyi temsil eder. Yani, 7.50 bir not için (10 - 7.50) / 10 = 0.25 olur, bu da circumference uzunluğunun değerinin 25% kadar kaydırılması gerektiği anlamına gelir:

 let note = parseFloat(display.dataset.note); let offset = circumference * (10 - note) / 10;

scripts.js güncelleme:

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { let progress = display.querySelector('.circle__progress--fill'); let radius = progress.r.baseVal.value; let circumference = 2 * Math.PI * radius; + let note = parseFloat(display.dataset.note); + let offset = circumference * (10 - note) / 10; progress.style.setProperty('--initialStroke', circumference); progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); + setTimeout(() => progress.style.strokeDashoffset = offset, 100); }); 
Vuruş özellikleri nota değerine geçiş
Vuruş özelliklerinin nota değerine geçişi (Büyük önizleme)

Devam etmeden önce, stoke geçişini kendi yöntemine çıkaralım:

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { - let progress = display.querySelector('.circle__progress--fill'); - let radius = progress.r.baseVal.value; - let circumference = 2 * Math.PI * radius; let note = parseFloat(display.dataset.note); - let offset = circumference * (10 - note) / 10; - progress.style.setProperty('--initialStroke', circumference); - progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); - setTimeout(() => progress.style.strokeDashoffset = offset, 100); + strokeTransition(display, note); }); + function strokeTransition(display, note) { + let progress = display.querySelector('.circle__progress--fill'); + let radius = progress.r.baseVal.value; + let circumference = 2 * Math.PI * radius; + let offset = circumference * (10 - note) / 10; + progress.style.setProperty('--initialStroke', circumference); + progress.style.setProperty('--transitionDuration', `${transitionDuration}ms`); + setTimeout(() => progress.style.strokeDashoffset = offset, 100); + }

Not Değer Artışı

Hala 0.00 oluşturulacak banknot değerine geçiş var. Yapılacak ilk şey, tamsayı ve ondalık değerleri ayırmaktır. string yöntemini kullanacağız split() (dizenin nerede kırılacağını belirleyen bir argüman alır ve her iki kırık dizeyi içeren bir dizi döndürür). Bunlar sayılara dönüştürülecek ve display öğesi ve bunun tamsayı mı yoksa ondalık sayı mı olduğunu belirten bir bayrakla birlikte increaseNumber() işlevine bağımsız değişkenler olarak iletilecektir.

 const displays = document.querySelectorAll('.note-display'); const transitionDuration = 900; displays.forEach(display => { let note = parseFloat(display.dataset.note); + let [int, dec] = display.dataset.note.split('.'); + [int, dec] = [Number(int), Number(dec)]; strokeTransition(display, note); + increaseNumber(display, int, 'int'); + increaseNumber(display, dec, 'dec'); });

boostNumber increaseNumber() işlevinde, className 'a bağlı olarak .percent__int veya .percent__dec öğesini seçiyoruz ve ayrıca çıktının bir ondalık nokta içermesi veya içermemesi durumunda. 900ms transitionDuration ayarladık. Şimdi, örneğin 0'dan 7'ye kadar bir sayıyı canlandırmak için sürenin 900 / 7 = 128.57ms . Sonuç, her artış yinelemesinin ne kadar süreceğini gösterir. Bu, setInterval her 128.57ms anlamına gelir.

Bu değişkenler set ile setInterval tanımlayalım. counter değişkeni, öğeye metin olarak eklenecek ve her yinelemede artırılacaktır:

 function increaseNumber(display, number, className) { let element = display.querySelector(`.percent__${className}`), decPoint = className === 'int' ? '.' : '', interval = transitionDuration / number, counter = 0; let increaseInterval = setInterval(() => { element.textContent = counter + decPoint; counter++; }, interval); } 
Sonsuz sayaç artışı
Sonsuz sayaç artışı (Büyük önizleme)

Serin! Değerleri arttırır, ancak sonsuza kadar yapar. Notalar istediğimiz değere ulaştığında setInterval temizlememiz gerekiyor. Bu clearInterval işleviyle yapılır:

 function increaseNumber(display, number, className) { let element = display.querySelector(`.percent__${className}`), decPoint = className === 'int' ? '.' : '', interval = transitionDuration / number, counter = 0; let increaseInterval = setInterval(() => { + if (counter === number) { window.clearInterval(increaseInterval); } element.textContent = counter + decPoint; counter++; }, interval); } 
Biten not görüntüleme projesi
Biten proje (Büyük önizleme)

Artık sayı not değerine kadar güncellenir ve clearInterval() işleviyle temizlenir.

Bu eğitim için bu kadar. Umarım eğlenmişsinizdir!

Biraz daha etkileşimli bir şey oluşturmak istiyorsanız, Vanilla JavaScript ile oluşturulan Hafıza Oyunu Eğitimime göz atın. Konumlandırma, perspektif, geçişler, Flexbox, olay işleme, zaman aşımları ve üçlüler gibi temel HTML5, CSS3 ve JavaScript kavramlarını kapsar.

Mutlu kodlama!