HTML5 SVG Animasyonu CSS3 ve Vanilla JavaScript ile Doldurun
Yayınlanan: 2022-03-10SVG, Ö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.

- Demo: Not Ekran Projesi
- Repo: Not Ekranı Repo
Dosya Yapısı
Dosyaları terminalde oluşturarak başlayalım:
mkdir note-display cd note-display touch index.html styles.css scripts.jsHTML
İş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 .

.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.

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:

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:

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 2vestroke-dasharray: 10 5 2 3) çeşitli boyutlarda tireler ve boşluklar oluşturacaktır.

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.

stroke-dasharray ve 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); } 
Ş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); }); 
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); } 
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); } 
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!
