練習にイチからSVGを書いてみようと思いました。
use
を使った物を作りたいので、同じ図形が複数出てくるもの...五輪!
今にぴったりなので作ってみることにしました。
著作権の問題でNGでした
もしやと思って調べたところ、
オリンピック・パラリンピックに関する知的財産等の無断使用および不正使用ないし流用は法的にも罰せられます。
オリンピック・パラリンピックに関するエンブレム、ロゴ、用語、名称をはじめとする知的財産は、日本国内では「商標法」、「不正競争防止法」、「著作権法」等により保護されています。
フリー素材でロゴ画像が手に入るぐらいなので大丈夫そうですが、こわいのでやめておきます。
(すでに登録していた五輪画像が記事のサムネイルになってしまっている。どうしたらいいのでしょう)
他の画像を探さなくては。
目標の画像
オリンピック開催中に作ろうという期限もなくなったので、せっかくならいろいろな要素を盛り込んだものを作りたいと思い、下記イラレで作った画像を目指します。
The 子供の絵!
子供の絵にはお日様が右上か左上によく描かれていますよね。太陽の存在感はすごい。
ポイントは
- 虹:グラデーションをかける
- 雲:useを使う、拡大縮小
- 太陽:光の線でuseを使う
です。
あわよくば太陽の光の線が回転して、雲が右から左に動かせたらいいなと思います。
作ってみました
動かす前と動かしたあとでコードが若干変わりました。以下は動かしています。
See the Pen GORIN by yokoyoko (@yokoyoko_code) on CodePen.
コードについて
今回は虹・雲・太陽をそれぞれのSVGの図形として、それらをまとめて1つのSVG画像としています。
1つずつコードを整えていきます。
虹
イラレでうまく作れず、ドーナツの半分の形にしたところ、グラデーションが崩れてしまいました。
パスだけ使って、グラデーションはsvgファイルを直接触ろうと思います。
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 380.46 190"> <defs> <style>.cls-1{fill:url(#名称未設定グラデーション_350);}</style> <radialGradient id="名称未設定グラデーション_350" cx="190.23" cy="95" r="150.35" gradientUnits="userSpaceOnUse"> <stop offset="0.7" stop-color="#c7659a"/> <stop offset="0.76" stop-color="#51527d"/> <stop offset="0.82" stop-color="#4facd2"/> <stop offset="0.88" stop-color="#369263"/> <stop offset="0.94" stop-color="#f4eb5f"/> <stop offset="1" stop-color="#d94f4f"/> </radialGradient> </defs> <g id="レイヤー_2" data-name="レイヤー 2"> <g id="レイヤー_1-2" data-name="レイヤー 1"> <path class="cls-1" d="M55.69,190a134.54,134.54,0,0,1,269.08,0h55.69C380.33,85,295.21,0,190.23,0S.13,85,0,190Z"/> </g> </g> </svg>
path
が虹の輪郭で、style
で塗りつぶし(fill
)をしています。
fill
プロパティでurlを radialGradient
に紐づけています。
そして radialGradient
は defs
で囲まれて定義され、見えなくなっています。
fill属性を使う
fill
はプロパティと属性、どちらもあります。
この場合 style
を使って path
にfillプロパティをあてていますが、直接 path
にfill属性を入れてもできます。
<path fill="url(#rainbow)" d="M55.69,190a134.54,134.54,0,0,1,269.08,0h55.69C380.33,85,295.21,0,190.23,0S.13,85,0,190Z"/>
これで style
は削除できます。
radialGradient要素の設定
radialGradient
は放射状のグラデーションを定義します。
cx、cy属性は、viewBoxのx、y座標を示し、r属性は半径を示します。
半円ドーナツ図形の中心がグラデーションの起点になってしまっていたので、xの座標は中央のままで、yの座標を図形の下辺にし、半径も半円ドーナツ図形をぴったり囲むように大きくします。
ちなみに gradientUnits="userSpaceOnUse"
についても調べましたが、分かりませんでした。
gradientUnits="userSpaceOnUse" の場合、 cx, cy, r, fx, fy は,グラデーション要素が参照された所における現在の利用座標系(すなわち fill あるいは stroke プロパティによりグラデーション要素を参照している要素の利用座標系)に対し, gradientTransform 属性で指定される変換を適用した結果の座標系における値を表す。
これを書かない、または gradientUnits="objectBoundingBox"
(初期値)だと、虹が真っ赤になってしまったので今回の場合は必要です。
gradientTransform属性を知る時が来れば分かるようになるかもしれません。
雲
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 121 80.67"> <defs> <style>.cls-1{fill:#fff;}</style> </defs> <g id="レイヤー_2" data-name="レイヤー 2"> <g id="レイヤー_1-2" data-name="レイヤー 1"> <path class="cls-1" d="M96.51,15.85a24.73,24.73,0,0,0-4.33.4A24.45,24.45,0,0,0,51.61,7.41a24.4,24.4,0,0,0-38.27,24.1A24.48,24.48,0,1,0,44.5,67.38a24.47,24.47,0,0,0,45-3.58,24.48,24.48,0,1,0,7-47.95Z"/> </g> </g> </svg>
雲は2つ出てくるのでuseを使ってみます。
viewBox,width,heightの値で雲の大きさ、x,yの値で雲の表示位置を設定する
1つのSVG画像として表示するにあたり、虹や雲など個別で書き出したSVG図形をどう配置するか混乱しましたが、MDNのページを見てこう考えました。
<symbol> - SVG: Scalable Vector Graphics | MDN
- まず定義側のviewBoxを考える。すでに書き出されたpathの座標(d)があるのでここはむやみに書き換えられない。viewBoxは図形いっぱいの枠と考える
- 定義側のwidth、heightを考える。これらの値に図形が拡大縮小する。そしてこのwidth、heightの値が呼び出し側の単位と合う
- 呼び出し側の
use
にwidth、height属性で大きさ、x、y属性で座標を指定して配置する
図にするとこういうイメージです。
もし symbol
にviewBox属性を指定していないと、たとえ symbol
にwidth、height属性を指定していても use
でwidth、height属性を指定して拡大・縮小できませんでした。もとの大きさのままです。
→ symbol
にviewBox属性は必須
もしsymbol
にwidth、height属性を指定していない場合、use
でwidth、height属性を指定していれば拡大・縮小できます。
このとき use
でwidth、height属性を指定していなければ呼び出し側のviewBoxの大きさいっぱいになります。
→ symbol
または use
でwidth、height属性を指定する
<use> - SVG: Scalable Vector Graphics | MDN
use 要素にあるx, y, width (en-US), height (en-US), href の各属性のみが参照される要素に設定されているものを上書きします。
複数の図形を拡大・縮小させて配置するなら use
でwidth、height属性を指定することになりそうです。
ちなみに、widthかheight片方のみを指定した場合(もう片方が未指定)、widthかheightの初期値は呼び出し側のviewBoxの大きさになりますが、おそらくpreserveAspectRatioの値がデフォルトであることが原因で、呼び出した図形はviewBoxの範囲内で上下左右中央に配置されます。
x、y属性でうまく配置するには、width、height属性で図形ぴったりの比率で指定する必要がありそうです。
先ほどの虹も結局 use
を使うことになりました。
太陽
最後は太陽です。
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 115.39 115.39"> <defs> <style>.cls-1{fill:#f08218;}.cls-2{fill:none;stroke:#f08218;stroke-miterlimit:10;stroke-width:4px;}</style> </defs> <g id="レイヤー_2" data-name="レイヤー 2"> <g id="レイヤー_1-2" data-name="レイヤー 1"> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="15.94" y1="57.7" y2="57.7"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="28.17" y1="87.22" x2="16.9" y2="98.49"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="57.7" y1="99.45" x2="57.7" y2="115.39"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="87.22" y1="87.22" x2="98.49" y2="98.49"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="99.45" y1="57.7" x2="115.39" y2="57.7"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="87.22" y1="28.17" x2="98.49" y2="16.9"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="57.7" y1="15.94" x2="57.7"/> <circle class="cls-1" cx="57.7" cy="57.7" r="30.28"/> <line class="cls-2" x1="28.17" y1="28.17" x2="16.9" y2="16.9"/> </g> </g> </svg>
line
は複数あるのは分かりますが何故か circle
も同じ数だけあります!
各属性値も同じなので、8つの円が重なっているようです。7つは消します。
この line
のx1、y1とx2、y2属性、1が始点で2が終点のx、y座標をあらわしています。
指定されていない場合、初期値は0です。
8つある line
のうち1つを使って、太陽の縁を中心に45度ずつ回転させて表示させてみよう!
...と当初は思ったのですが、use
で太陽の光の線を8回呼び出すのはどうなのか、それだと後々の「光の線を回転させる」が難しいのではと思い、まとめて呼び出すことにしました。
symbolを呼び出すのかgを呼び出すのか
太陽の図形は「円」と「光の線」で分けて出したいので、ざっくりと下記のようにしました。
定義側の太陽
<symbol> <g> <circle> </g> <g> <line> : (✕ 7) </g> </symbol>
symbol
と g
はid属性を振れば use
で呼び出せます。
この場合は太陽まるごと呼び出すときは symbol
、「円」と「光の線」で2回呼び出すときは g
を使ってできます。
2つは呼び出したときのhtmlへの出力が異なります。
symbol
の場合は、symbol
が svg
に置き換わった形で出力されます。 中で g
に「円」と「光の線」がそれぞれ囲まれています。
一方 g
の場合はそのまま出力されます。
なので「円」と「光の線」で分けられていながら、1つの図形としてまとまっている symbol
で呼び出すことにしました。
2つのg要素をさらにg要素で囲い、それを呼び出す方法もありますが、g要素は呼び出し側でwidthとheightの指定ができません。
symbol
と g
の使い分けは、いろいろ作っていくうちに慣れてきそうな気がしました。
迷ったら symbol
で囲います。
classとスタイルのあて方
path
や g
、use
など様々な要素でスタイルをあてることができるので、何がベストなのか大混乱です。
大事なのは定義側で指定したスタイルを呼び出し側で上書きできないということです。
雲のところで大きさの上書き指定は可能だがその他はできない、というのはこの事だったのですね!
定義側の path
にfill: red
とした場合、呼び出し側の use
に fill: blue
と指定しても青にはなりません。
そう考えると、呼び出し側に統一してスタイルをあてた方が良いのでしょうか。
先の「symbolを呼び出すのかgを呼び出すのか」問題とも関係してくるので、状況によりけりなのかなと思いました。
ちなみにデベロッパーツールでスタイルを確認したときにつまづきました。定義側と呼び出し側、それぞれを確認しないといけないです。
定義側のsvgのスペースを削除する
SVG画像の前に、定義したsvg要素に謎の300x150pxの隙間ができました。
svg
に display="none"
を書いたり、cssで svg { display: none; }
を指定するとうまくいくと思ったのですが、
なんと虹が消えてしまいました。
虹で使っている radialGradient
が display: none
で無効になってしまったのだと思います。
そこで色々と試行錯誤したところ、
/* 定義側のsvg */ .svg-defs { display: block; width: 0; height: 0; }
としたところ、隙間が消えてくれました。
他のケースでも使えるといいなと思います。
できあがったSVG(動かない)
これで虹・雲・太陽がそろいました!
大きさや位置が目分量ですが真似できました◎
HTML
<!-- 定義側 --> <svg class="svg-defs"> <defs> <!-- 虹 --> <symbol id="rainbow" viewBox="0 0 380.46 190"> <radialGradient id="rainbow-gra" cx="190.23" cy="190" r="190.23" gradientUnits="userSpaceOnUse"> <stop offset="0.7" stop-color="#c7659a"/> <stop offset="0.76" stop-color="#51527d"/> <stop offset="0.82" stop-color="#4facd2"/> <stop offset="0.88" stop-color="#369263"/> <stop offset="0.94" stop-color="#f4eb5f"/> <stop offset="1" stop-color="#d94f4f"/> </radialGradient> <path fill="url(#rainbow-gra)" d="M55.69,190a134.54,134.54,0,0,1,269.08,0h55.69C380.33,85,295.21,0,190.23,0S.13,85,0,190Z"/> </symbol> <!-- 雲 --> <symbol id="cloud" viewBox="0 0 121 80.67"> <path d="M96.51,15.85a24.73,24.73,0,0,0-4.33.4A24.45,24.45,0,0,0,51.61,7.41a24.4,24.4,0,0,0-38.27,24.1A24.48,24.48,0,1,0,44.5,67.38a24.47,24.47,0,0,0,45-3.58,24.48,24.48,0,1,0,7-47.95Z"/> </symbol> <!-- 太陽 --> <symbol id="sun" viewBox="0 0 115.39 115.39"> <g id="sun-circle"> <circle class="sun-circle" cx="57.7" cy="57.7" r="30.28"/> </g> <g id="sun-line" class="sun-line"> <line x1="15.94" y1="57.7" y2="57.7"/> <line x1="28.17" y1="87.22" x2="16.9" y2="98.49"/> <line x1="57.7" y1="99.45" x2="57.7" y2="115.39"/> <line x1="87.22" y1="87.22" x2="98.49" y2="98.49"/> <line x1="99.45" y1="57.7" x2="115.39" y2="57.7"/> <line x1="87.22" y1="28.17" x2="98.49" y2="16.9"/> <line x1="57.7" y1="15.94" x2="57.7"/> <line x1="28.17" y1="28.17" x2="16.9" y2="16.9"/> </g> </symbol> </defs> </svg> <!-- 呼び出し側 --> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="oekaki-area" width="500" height="300" viewBox="0 0 500 300" role="img" aria-labelledby="oekaki"> <!-- 雲 --> <use href="#cloud" class="cloud" x="250" y="10" width="140" height="93.3"></use> <use href="#cloud" class="cloud" x="350" y="160" width="120" height="80"></use> <!-- 虹 --> <use href="#rainbow" x="75" y="125" width="350" height="175"></use> <!-- 太陽 --> <use href="#sun" x="20" y="20" width="130" height="130"></use> <!-- 太陽のパーツを個別に出す場合 --> <!-- <use class="sun-circle" href="#sun-circle" x="20" y="20" width="130" height="130"></use> <use class="sun-line" href="#sun-line" x="20" y="20" width="130" height="130"></use> </svg> -->
.svg-defs { display: block; width: 0; height: 0; } #oekaki-area { border: 1px solid #000; background-color: #def0f3; } .cloud { fill: #fff; } .sun-circle { fill: #f08218; } .sun-line { stroke: #f08218; stroke-miterlimit: 10; stroke-width: 4px; }
雲を動かす
さらにSVGの図形に動きを加えてみます。
雲は静止画の位置から左へ移動して、また右から現れるようにします。
こちらを見ながら書いたらばっちりだと思います。
SVG アニメーション(SMIL を使ったアニメーション) / Web Design Leaves
せっかくならSVGの中で動かしたい...!
SMIL(Synchronized Multimedia Integration Language)というマークアップ言語を使う方法がそれにあたるようです。
SVG animation with SMIL - SVG: Scalable Vector Graphics | MDN
SMIL では以下のようなことができます: ・要素の数値属性 (x, y など) のアニメーション ・トランスフォーム属性 (translation または rotation) のアニメーション ・色属性のアニメーション ・モーションパスに従う
具体的には animateMotion
や animate
など、アニメーションに関係するSVG要素を書きます。
cssを書かずにアニメーションできます。
animateMotion、animate要素が使えるかどうか
はじめは animateMotion
を使うのが適当と思いましたが、pathに沿った動きをさせたい場合に使うようです。
ふわふわ上下しながら流れていって欲しいのが正直なところですが、ここは右から左に水平に動かします。
続いて animate
を考えましたが、こちらは要素の属性のアニメーションに使うものなので、雲の path
には位置に関わる属性(x,y)がなくて動かせない。
雲を変形させることはできます。
cx,cy属性のある circle
であれば animate
を使って移動のアニメーションができます。
呼び出し側で使えない。定義側も問題あり
animate
などのアニメーション要素は use
と絡めて書くことはできないのではと思いました。
アニメーション要素は、 path
など形を表す要素の子要素になるように書きます。
<path><animate></path>
です。
path
で囲む書き方もできるのですね。
use
を使うと svg
で書き出されるので、animate
を差し込む余地がありません。
また、定義側でアニメーションの定義をすればよいのではと考えましたが、雲の定義の仕方では無理そうです。 定義側では雲の形状だけを設定していて、viewBoxが雲いっぱいのサイズのため雲が移動するスペースがありません。
呼び出し側と同じviewBoxで、定義側で雲が配置されていれば可能なのかもしれませんが、まだ今の知識では書けません。
となると、雲の場合はcssに頼るしかないのかもしれません。
cssのanimationプロパティで動かす
静止画の位置から右から左に移動させる、というところで少しつまずきました。
0%→100%で右端(見えない)→左端(見えない)とすると、最初の表示のタイミングでは雲が見えません。
その場合は animation-delay プロパティにマイナスの値を設定するとうまくいきます。
遅らすのではなく早めることができるので、0%→100%の途中からスタートできます。
10秒かけた移動だから...とこれも目分量で設定しました。
雲は動きました!
太陽の光を動かす
こちらはSMIL で動かすことができました。
animateTransform要素で回転させる
参考ページなどを見てすんなりとできました。
g
で囲った中で animateTransform
を書いて g
内の要素の動きを設定できます。
ちなみにデフォルトでは回転は時計回り(右回り)ですが、to="-360, ..."
とマイナスの値だと左回りになりました。
さいごに
当初の五輪だったらここまで長くならなかったですね。
SVGはまだまだ奥深いことがわかりました。
※検証段階の内容が多分に含まれているので、分かり次第修正していきます。
順番が前後してしまいますが、この後SVGについてまとめた記事を書きます。
実践が大事!おしまい!