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

.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 2
vestroke-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!