CSS3とVanillaJavaScriptを使用したHTML5SVG塗りつぶしアニメーション

公開: 2022-03-10
簡単な要約↬この記事では、AwwwardsWebサイトからアニメーションのノート表示を作成する方法を学ぶことができます。 HTML5 SVG円要素、そのストロークプロパティ、およびCSS変数とVanillaJavaScriptを使用してそれらをアニメーション化する方法について説明します。

SVGは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>

各音符要素は、 circlenote価、およびそのlabelを保持するリスト項目liで構成されます。

アイテム要素と直接の子を一覧表示します
アイテム要素とその直接の子を一覧表示します: .circle.percent.label 。 (大プレビュー)

.circle_svgはSVG要素であり、2つの<circle>要素をラップします。 1つ目は塗りつぶされるパスで、2つ目はアニメーション化される塗りつぶしです。

SVG要素
SVG要素。 SVGラッパーとサークルタグ。 (大プレビュー)

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で、 blockelementmodifierを表します。 これは、要素の命名をより構造化され、整理され、意味論的にする方法です。

推奨読書BEMの説明となぜそれが必要なのか

テンプレート構造を完成させるために、4つのリストアイテムを順序付けられていないリスト要素でラップしましょう。

順序付けされていないリストラッパー
順序付けされていないリストラッパーは、4つのli子を保持します(大きなプレビュー)
 <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>

TransparentReasonableUsableExemplaryなラベルが何を意味するのかを自問する必要があります。 プログラミングに精通しているほど、コードの記述はアプリケーションを機能させるだけでなく、長期的に保守可能でスケーラブルであることを保証することでもあることに気付くでしょう。 これは、コードを簡単に変更できる場合にのみ実現されます。

「頭字語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-containermargin-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-widthstroke-opacitystroke-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-dasharraystroke-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 )は、さまざまなサイズのダッシュとギャップを生成します。
ストロークdasharrayプロパティ値
stroke-dasharrayプロパティ値(大きなプレビュー)

左の画像は、プロパティstroke-dasharrayが0から238px(円周の長さ)に設定されていることを示しています。

2番目の画像は、ダッシュ配列の先頭をオフセットするstroke-dashoffsetプロパティを表しています。 また、0から円周長まで設定されます。

ストロークdasharrayおよびdashoffsetプロパティ
stroke-dasharrayストローク-ダッシュオフセットプロパティ(大プレビュー)

塗りつぶし効果を生成するために、 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要素を選択することから始めましょう。 transitionDuration900ミリ秒に設定されます。

次に、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要素のいずれかを選択します。 transitionDuration900msに設定しました。 ここで、たとえば0から7までの数値をアニメーション化するには、期間をノート900 / 7 = 128.57msで割る必要があります。 結果は、各増加の反復にかかる時間を表します。 これは、 setInterval128.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の基本的な概念について説明しています。

ハッピーコーディング!