CSS3とVanillaJavaScriptを使用したHTML5SVG塗りつぶしアニメーション
公開: 2022-03-10SVGはScalable V ector G raphicsの略で、ベクターグラフィックス用の標準のXMLベースのマークアップ言語です。 2D平面内の一連の点を決定することにより、パス、曲線、および形状を描画できます。 さらに、アニメーションを作成するために、これらのパスに単収縮プロパティ(ストローク、色、太さ、塗りつぶしなど)を追加できます。
2017年4月以降、CSSレベル3の塗りつぶしとストロークモジュールでは、各要素に属性を設定する代わりに、外部スタイルシートからSVGの色と塗りつぶしパターンを設定できます。 このチュートリアルでは、単純なプレーンな16進色を使用しますが、塗りと線の両方のプロパティは、パターン、グラデーション、および画像も値として受け入れます。
注: Awwwards Webサイトにアクセスすると、アニメーション化されたメモの表示は、ブラウザーの幅が1024px以上に設定されている場合にのみ表示できます。
- デモ:ノート表示プロジェクト
- リポジトリ:メモ表示リポジトリ
ファイル構造
ターミナルでファイルを作成することから始めましょう:
mkdir note-display cd note-display touch index.html styles.css scripts.js
HTML
css
ファイルとjs
ファイルの両方をリンクする初期テンプレートは次のとおりです。
<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>
各音符要素は、 circle
、 note
価、およびそのlabel
を保持するリスト項目li
で構成されます。
.circle_svg
はSVG要素であり、2つの<circle>要素をラップします。 1つ目は塗りつぶされるパスで、2つ目はアニメーション化される塗りつぶしです。
note
は整数と小数に分けられているため、さまざまなフォントサイズを適用できます。 label
は単純な<span>
です。 したがって、これらすべてをまとめると、次のようになります。
<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
属性とcy
属性は、円のx軸とy軸の中心点を定義します。 r
属性はその半径を定義します。
クラス名のアンダースコア/ダッシュパターンに気づいたかもしれません。 これがBEMで、 block
、 element
、 modifier
を表します。 これは、要素の命名をより構造化され、整理され、意味論的にする方法です。
推奨読書: BEMの説明となぜそれが必要なのか
テンプレート構造を完成させるために、4つのリストアイテムを順序付けられていないリスト要素でラップしましょう。
<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
、 Exemplary
なラベルが何を意味するのかを自問する必要があります。 プログラミングに精通しているほど、コードの記述はアプリケーションを機能させるだけでなく、長期的に保守可能でスケーラブルであることを保証することでもあることに気付くでしょう。 これは、コードを簡単に変更できる場合にのみ実現されます。
「頭字語TRUE
は、作成したコードが将来の変更に対応できるかどうかを判断するのに役立ちます。」
だから、次回は、自問してみてください。
-
Transparent
的:コード変更の結果は明確ですか? -
Reasonable
:費用便益はそれだけの価値がありますか? -
Usable
:予期しないシナリオで再利用できますか? -
Exemplary
:将来のコードの例として高品質を示していますか?
注: Sandi Metzによる「Rubyでの実用的なオブジェクト指向の設計」では、 TRUE
を他の原則とともに説明し、設計パターンを通じてそれらを実現する方法を説明しています。 デザインパターンをまだ勉強していない場合は、この本を就寝時の読書に追加することを検討してください。
CSS
フォントをインポートして、すべてのアイテムにリセットを適用してみましょう。
@import url('https://fonts.googleapis.com/css?family=Nixie+One|Raleway:200'); * { padding: 0; margin: 0; box-sizing: border-box; }
box-sizing: border-box
プロパティには、要素の合計の幅と高さにパディングと境界線の値が含まれているため、要素の寸法を簡単に計算できます。
注: box-sizing
の視覚的な説明については、「CSSボックスサイジングで生活を楽にする」をお読みください。
body { height: 100vh; color: #fff; display: flex; background: #3E423A; font-family: 'Nixie One', cursive; } .display-container { margin: auto; display: flex; }
ルールdisplay: flex
body
のflexと.display-container
のmargin-auto
を組み合わせることで、子要素を垂直方向と水平方向の両方の中央に配置することができます。 .display-container
要素もflex-container
なります。 そうすれば、その子は主軸に沿って同じ行に配置されます。
.note-display
リストアイテムもflex-container
なります。 センタリングする子はたくさんあるので、 justify-content
プロパティとalign-items
プロパティを使って行いましょう。 すべてのflex-items
は、 cross
軸とmain
に沿って中央に配置されます。 それらが何であるかわからない場合は、「CSS Flexbox FundamentalsVisualGuide」の配置セクションを確認してください。
.note-display { display: flex; flex-direction: column; align-items: center; margin: 0 25px; }
ストロークのライブ終了を完全にスタイル設定するルールstroke-width
、 stroke-opacity
、 stroke-linecap
を設定して、円にストロークを適用してみましょう。 次に、各円に色を追加しましょう。
.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
要素を絶対的に配置するには、何を絶対に知る必要があります。 .circle
要素は参照である必要があるので、それを基準にしてposition: relative
を追加しましょう。
注:絶対ポジショニングの詳細な視覚的説明については、「CSSポジショニングを完全に理解する方法」をお読みください。
要素を中央に配置する別の方法は、 top: 50%
、 left: 50%
、およびtransform: translate(-50%, -50%);
を組み合わせることです。 要素の中心をその親の中心に配置します。
.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; }
これで、テンプレートは次のようになります。
塗りつぶし遷移
円のアニメーションは、2つの円のSVGプロパティ( stroke-dasharray
とstroke-dashoffset
を使用して作成できます。
「 stroke-dasharray
は、ストロークのダッシュギャップパターンを定義します。」
最大4つの値を取ることができます。
- 整数のみに設定されている場合(
stroke-dasharray: 10
)、ダッシュとギャップのサイズは同じです。 - 2つの値(
stroke-dasharray: 10 5
)の場合、最初の値はダッシュに適用され、2番目の値はギャップに適用されます。 - 3番目と4番目の形式(
stroke-dasharray: 10 5 2
およびstroke-dasharray: 10 5 2 3
)は、さまざまなサイズのダッシュとギャップを生成します。
左の画像は、プロパティstroke-dasharray
が0から238px(円周の長さ)に設定されていることを示しています。
2番目の画像は、ダッシュ配列の先頭をオフセットするstroke-dashoffset
プロパティを表しています。 また、0から円周長まで設定されます。
塗りつぶし効果を生成するために、 stroke-dasharray
を円周の長さに設定します。これにより、すべての長さが大きなダッシュで埋められ、ギャップがなくなります。 また、同じ値でオフセットするため、「非表示」になります。 次に、 stroke-dashoffset
が対応する音価に更新され、トランジション期間に応じてストロークが塗りつぶされます。
プロパティの更新は、CSS変数を介してスクリプトで行われます。 変数を宣言し、プロパティを設定しましょう。
.circle__progress--fill { --initialStroke: 0; --transitionDuration: 0; stroke-opacity: 1; stroke-dasharray: var(--initialStroke); stroke-dashoffset: var(--initialStroke); transition: stroke-dashoffset var(--transitionDuration) ease; }
初期値を設定して変数を更新するために、 document.querySelectorAll
ですべての.note-display
要素を選択することから始めましょう。 transitionDuration
は900
ミリ秒に設定されます。
次に、displays配列を反復処理し、その.circle__progress.circle__progress--fill
を選択し、HTMLで設定されたr
属性を抽出して、円周の長さを計算します。 これで、初期の--dasharray
値と--dashoffset
値を設定できます。
--dashoffset
変数が100msのsetTimeoutによって更新されると、アニメーションが発生します。
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); });
上から遷移を取得するには、 .circle__svg
要素を回転させる必要があります。
.circle__svg { transform: rotate(-90deg); }
それでは、ノートに関連するdashoffset
値を計算してみましょう。 音価は、data- *属性を介して各li
アイテムに挿入されます。 *
は、ニーズに合った任意の名前に切り替えることができ、その後、要素のデータセットelement.dataset.*
を介してJavaScriptで取得できます。
注: MDN WebDocsのdata- *属性について詳しく読むことができます。
私たちの属性は「 data-note
」と呼ばれます:
<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
メソッドは、 display.dataset.note
によって返される文字列を浮動小数点数に変換します。 offset
は、最大スコアに到達するために欠落しているパーセンテージを表します。 したがって、 7.50
の音符の場合、 (10 - 7.50) / 10 = 0.25
になります。これは、 circumference
の長さをその値の25%
だけオフセットする必要があることを意味します。
let note = parseFloat(display.dataset.note); let offset = circumference * (10 - note) / 10;
scripts.js
の更新:
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); });
先に進む前に、ストーク遷移を独自のメソッドに抽出しましょう。
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); + }
音価の増加
0.00
から構築される音価への音価遷移はまだあります。 最初に行うことは、整数値と10進値を分離することです。 文字列メソッドsplit()
を使用します(文字列が壊れている場所を決定する引数を取り、両方の壊れた文字列を含む配列を返します)。 これらは数値に変換され、引数としてincreaseNumber()
関数に渡されます。また、 display
要素と、整数か10進数かを示すフラグも渡されます。
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'); });
へのincreaseNumber()
関数では、 className
に応じて、また出力に小数点が含まれるかどうかに応じて、 .percent__int
要素または.percent__dec
要素のいずれかを選択します。 transitionDuration
を900ms
に設定しました。 ここで、たとえば0から7までの数値をアニメーション化するには、期間をノート900 / 7 = 128.57ms
で割る必要があります。 結果は、各増加の反復にかかる時間を表します。 これは、 setInterval
が128.57ms
ごとに起動することを意味します。
これらの変数を設定したら、 setInterval
を定義しましょう。 counter
変数はテキストとして要素に追加され、反復ごとに増加します。
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); }
いいね! それは値を増やしますが、それは一種の永遠にそれを行います。 ノートが必要な値に達したら、 setInterval
をクリアする必要があります。 これはclearInterval
関数で行われます。
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); }
これで、数値が音価まで更新され、 clearInterval()
関数でクリアされます。
このチュートリアルはこれでほぼ完了です。 楽しんでいただけたでしょうか!
もう少しインタラクティブなものを作成したい場合は、VanillaJavaScriptで作成したメモリゲームチュートリアルを確認してください。 ポジショニング、パースペクティブ、トランジション、フレックスボックス、イベント処理、タイムアウト、ターナリなど、HTML5、CSS3、JavaScriptの基本的な概念について説明しています。
ハッピーコーディング!