1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

日本語プログラミング言語「なでしこ」Advent Calendar 2022

Day 23

なでしこさんでクリスマスツリーを飾るよ!

Last updated at Posted at 2022-12-23

 もうクリスマスツリーは飾り付けましたか?
 日本では住宅事情でなかなか大きなツリーは飾れませんよね~。
 壁に押っつけて飾れるように、なんと半分しかないツリーなどもあるそうです(縦に真っ二つ!)
 ウチでも飾らなくなって久しいです。
 とゆうわけで、今年はパソコン上にクリスマスツリーを作って飾り付けしてみたいと思います!

SVGでやります

 同様のことはもちろんキャンバスでも出来ると思いますが、せっかくいろいろ頑張ったので、SVGでやってみます!
 特に、オーナメントをマウスで飾り付けするなどは、頑張ってドラッグ&ドロップを実装した甲斐あって、いちいち座標を取らなくて良い分カンタンに出来そうです。
 これまで記事にした件の実践編、記事にはしてない件の紹介もできたらと思います。

 とりあえず普通にお絵かきする要領で、ツリーを描いていきます。

背景

# 親SVG作成
変数 [画面幅,画面高]=[380,520]。
ツリー画像=[画面幅,画面高]のSVG親要素作成。# svg

# 背景
ツリー画像へSVG描画開始。
変数 地平線=画面幅。

## 空
アリスブルー色にSVG塗り色設定。
背景空=[0,0,画面幅,地平線]へSVG四角描画。

## 地面
ベージュ色にSVG塗り色設定。
背景地面=[0,地平線,画面幅,画面高さ-地平線]へSVG四角描画。

 まずは大本となるsvg要素を作成して、空の部分と地面の部分を塗り分けるように四角を描いただけですね:sweat_smile:

 「空」と「地面」にしたかったけどはなでしこさんでは「から」と読み、空っぽの文字列を表す予約語となっているので変数として使えません。
 普段から空を使っているのに、読み方変わったらうっかり使ってアレ?ってなっちゃうワタクシです💧
 「そら」「から」「くう」「あき」・・・日本語は日本語でむじゅいw

 ところで、v1ではだいたい何でもでOKだったのですが、v3では文字列は空ですが配列は空配列、辞書型変数は空辞書と使い分けなければならず、一方、0がフツーにやると同一の判定になってしまい等しい==などで厳密な判定を行わないと区別出来なかったり、さらには未定義(undefined)やNULL(null)といった概念も入っているので注意が必要ですね。
 v1時代に比べてなでしこさん、ドシロウトに優しくにゃい……:crying_cat_face:

グラデーション

 さすがにしょぼいので、空《そら》にはグラデーションをかけてみます。
 空部分をちょこっと書き換え。

## 空
定義=ツリー画像へSVG定義領域作成。# defs
stop=[["0%","#000033"],["100%",アリスブルー色]]。
x1,x2,y1,y2=[0,0,0,1]
グラ空=定義へ[x1,y1]から[x2,y2]までstopでSVG線形グラデーション作成。

背景空=[0,0,画面幅,地平線]へSVG四角描画。
背景空の「塗り色」にグラ空をSVGグラデーション設定。

 stopタグで指定するオフセット、色、透明度の情報を、まるっと配列で指定してグラデーションを作成。
 先に定義領域(defs)にグラデーションを作成しておいて、それを図形の塗り色にグラデーション設定する感じ。

※ グラデーションとかフィルターとかパターンとかそうゆうのは、全てdefsに記述せよとゆうことになっているようなので、(別にしなくても出来ることは出来るみたいなんですが)いちおうそうする。

ツリー

#ツリー
変数 [頂点,中心]=[65,画面幅/2]。

## 幹
茶色にSVG塗り色設定。
[中心-20,頂点+200,40,50]へSVG四角描画。

 またまたただの四角!
 なんで真っ先に幹を描いたかと言うと、描くのが簡単だから! とゆうわけではなく、描いた順に重なって行くので、これを一番後ろにしておけば葉っぱと鉢をきっちり配置しなくても、いい感じに表示されるからなのですぅ。

 ツリーの本体と言うべき葉の部分ですが、パスを頑張ってベジェ曲線など極めれば色々とかっこよさげな形が作れそうですが、今回はよくある三角を重ねたような形にします。
 三角は、SVG多角形描画で描けます。
 ポリゴンには角丸の設定がありませんが、線をすっごく太くして、線の角形状(stroke-linejoin)を丸(round)に設定すれば丸くなります☆

## 葉
「#009900」にSVG塗り色設定。「#009900」にSVG線色設定。
15にSVG線太さ設定。「丸」にSVG角形状設定。
葉頂点=頂点。
3回:
    枝張り=65+30*(回数-1)。葉底辺=葉頂点+枝張り。
    [[中心,葉頂点],[中心-枝張り,葉底辺],[中心+枝張り,葉底辺]]のSVG多角形描画。
    葉頂点=葉頂点+枝張り/2。

 三角の座標は、ちょっと賢そうな感じでアレしてみようと思いましたが、それほどでもない💧

## 鉢
10にSVG線太さ設定。
鉢=[[中心-50,頂点+250],[中心+50,頂点+250],[中心+40,頂点+310],[中心-40,頂点+310]]のSVG多角形描画。

 鉢も同様に多角形描画で、地道に座標を指定して台形を描きました。
 色はまだ指定していません。
 ちょっとかっこよくなるよう、パターンとフィルターを設定してみます。

タイルパターンとフィルター

### 鉢のパターン
レンガ=定義に[40,20]でSVGタイルパターン作成。# pattern
レンガへSVG描画開始。

# 鉢の地色
無色にSVG線色設定。「#CC5533」にSVG塗り色設定。1にSVG線太さ設定。
[0,0,40,20]へSVG四角描画。

# レンガの目地
# まとめてフィルターが掛けられるようグループにする
目地=レンガへSVGグループ作成。# g
目地へSVG描画開始。
白色にSVG塗り色設定。
[0,0,40,2]へSVG四角描画。[0,10,40,2]へSVG四角描画。
[0,0,2,10]へSVG四角描画。[20,0,2,10]へSVG四角描画。
[10,10,2,10]へSVG四角描画。[30,10,2,10]へSVG四角描画。

# 目地にかけるフィルター
フィルター鉢=定義にSVGフィルター作成。# filter
SVG対象フィルター=フィルター鉢。
目地を3にSVG画像粒子拡散。

# パターンを適用
鉢の「塗り色」にレンガをSVGタイルパターン設定。
鉢の「線色」にレンガをSVGタイルパターン設定。

 これも先に定義領域にタイルパターンを作って、そこにパターン画像を描画。
 それを塗り色や線色にタイルパターン設定することで、パターンに描画した物で埋め尽くされるって寸法です。

 フィルターは、途中からフィルター作るの方が楽しくなってしまい、いろいろ調べて結構いろいろ作ってみたのですがそれについて語れるほどは賢くなってもない💧
 ってかSVGのフィルターがいろいろ複雑すぎてイマイチ便利につかえるようにならない~。
 とりあえず、定義領域にSVGフィルター作成して、それをSVG対象フィルターに操作対象となるフィルターを設定してから実際のフィルターを追加していく的な?

 ともかくこんなパターンになりまして、
2022-12-22 (2).png
 それをタイルパターン設定すれば・・・
2022-12-22 (1).png
 できました!

 ところで、なんか地面が無駄に広くない? ってなるでしょうが、あっています!
 ここに、飾り付け用のオーナメントを置いていきます☆

オーナメント各種を描画

 ツリーは描画したい座標を指定して、直接描画していきましたがオーナメントは小物だし、動かせるようにするので、描画した後に描画移動(translate)することにします。

星.png
 ツリーのてっぺんには星が欲しいよね~。
 このためだけにお星様描く関数作った~。

 ユキノはさいんこさいんのじゅもんをとなえた!

●(数で中点に半径の|中点へ)SVG星描画
    外径,内径=半径。
    もし、内径=未定義ならば、内径は外径/2。
    P=空配列。
    数*2回
        角度=(360/(数*2))*(回数-1)。
        もし、回数%2=1ならば、半径=外径。
        違えば、半径=内径。
        x=半径*COS(((角度-90)をラジアン変換))+中点[0]。
        y=半径*SIN(((角度-90)をラジアン変換))+中点[1]。
        Pへ[x,y]を配列追加。
    ここまで。
    PでSVG多角形描画。
ここまで。

 外径と内径を指定して、太った星やほっそい星も描けるようにしています。
 そして、

黄色にSVG塗り色設定。
星=5で[40,40]に[40,18]のSVG星描画。
星を[20,地平線+20]へSVG描画移動。

 0,0起点で普通に描いて、SVG描画移動で地面に配置。
 並べる順番を変えたくなっても安心♪

キャンディ

飴.png
 線と円弧をくっつけてステッキ型にして、紅白の縞々のパターンを設定。
 ちょこっと回転。

縞=定義に[10,10]でSVGタイルパターン作成。
縞へSVG描画開始。
赤色にSVG塗り色設定。赤色にSVG線色設定。
[0,0,10,10]へSVG四角描画。
白色にSVG線色設定。3にSVG線太さ設定。「四角」にSVG線端形状設定。
[5,0]から[0,5]へSVG線描画。
[10,5]から[5,10]へSVG線描画。

飴=ツリー画像へSVGグループ作成。
飴へSVG描画開始。
無色にSVG塗り色設定。空にSVG線色設定。10にSVG線太さ設定。「丸」にSVG線端形状設定。
[16,16]へ(180をラジアン変換)から0まで8のSVG円弧描画。
[24,16]から[24,50]へSVG線描画。

飴の「線色」に縞をSVGタイルパターン設定。
飴を[120,地平線+25]へSVG描画移動。
飴を30だけSVG描画回転。

プレゼント

プレゼント.png
 本体はただの角丸四角なんですが、リボンが・・・
 パスとかベジェ曲線とか極めれば、ひらひらのリボンなども描けるのだろうけれど・・・ムリ~。

プレゼント=ツリー画像へSVGグループ作成。
プレゼントへSVG描画開始。
黄色にSVG塗り色設定。無色にSVG線色設定。1にSVG線太さ設定。
[0,12,30,24]へ[3,3]でSVG角丸四角描画。
赤色にSVG塗り色設定。
[13,12,4,24]へSVG四角描画。
[0,22,30,4]へSVG四角描画。
[15,12]へSVG連続パス描画開始。
    [27,3]まで[23,0]でSVG二次ベジェ曲線描画。
    [15,12]まで[30,10]でSVG二次ベジェ曲線描画。
    [3,3]まで[0,10]でSVG二次ベジェ曲線描画。
    [15,12]まで[7,0]でSVG二次ベジェ曲線描画。
SVG連続パス描画終了。
[15,12]へ[3,2]のSVG楕円描画。

プレゼントを[160,地平線+40]へSVG描画移動。

靴下

靴下.png
 疲れてきたので凝ったことはせず、四角と円を組み合わせて、ぽい形にしている:sweat_smile:

赤色にSVG塗り色設定。赤色にSVG線色設定。1にSVG線太さ設定。
靴下=ツリー画像へSVGグループ作成。
靴下へSVG描画開始。
[30,5,20,35]へSVG四角描画。
[20,30,20,20]へSVG四角描画。
[20,40]へ10のSVG円描画。
[40,40]へ10のSVG円描画。
白色にSVG塗り色設定。
[28,3,24,12]へ3のSVG角丸四角描画。

靴下を[200,地平線+30]へSVG描画移動。
靴下を10だけSVG描画回転。

飾り玉

玉.png
 だいたいいっぱい入ってる玉っころは、リンゴのイメージらしいです。

シンボル

 同じ形を複数作るので、シンボルを一個だけ書いて、複製していくことに。
 シンボルグループ(symbol)に元となる玉を描いて、照明フィルターで反射光をつけて球にします。
 放射グラデーションでも良かったけど、光源フィルターとかがようやく分かって使える感じになったのでうれしがって使っていますが、たいしたことない:sweat_smile:

フィルター球=定義にSVGフィルター作成。# filter
玉シンボル=定義へSVGシンボルグループ作成。# symbol
空にSVG塗り色設定。
玉シンボルへSVG描画開始。
[50,70]へ50のSVG円描画。
[40,4,20,20]へSVG四角描画。

# フィルター
SVG対象フィルター=フィルター球。
照明=[白色]で玉シンボルへSVG照明効果。
照明のDOM要素ID取得の「surfaceScale」に4をSVG属性設定。
照明に[30,45,30]のSVG点光源設定。
「」に5のSVG画像ボカシ。
「元画像」と「」を「lighten」でSVG画像ブレンド。
「」と「影」を「atop」でSVG画像合成。

# 移動
ツリー画像へSVG描画開始。
玉色=[黄色,赤色,紫色,青色]
玉位置=[[280,30],[320,30],[280,70],[320,70]]
飾玉は空配列。
4回:
    番号=回数-1。
    x,y=玉位置[番号]。
    飾玉[番号]=玉シンボルをSVG参照複製。
    飾玉[番号]の「塗り色」に玉色[番号]をSVG属性設定。
    [x,地平線+y]へ飾玉[番号]をSVG描画移動。
    飾玉[番号]を0.2だけSVG描画拡大。

 ちなみに・・・

[x,地平線+y]へ飾玉[番号]をSVG描画移動。

 だけ、他と順番が違いますが、他と同じく

飾玉[回数-1]を[x,地平線+y]へSVG描画移動。

 とすると、エラーなります💧
 これは、なでしこさんの持病のようです。
 引数に配列を指定した後に、配列で指定する引数が続くと変なるのねん:sweat:
 このように順番を入替えるか、カッコで括るとかすればだいじょぶ!

電飾

 クリスマスツリーはやっぱり、電気がピカピカ点滅するイメージですよね~。
 これも同じのたくさん作るから、シンボルにいっこ描画して参照複製。

電飾シンボル=定義へSVGシンボルグループ作成。
電飾シンボルへSVG描画開始。
# 画像ボカシで光ってる感じにする
「rgba(255,255,100,0.5)」にSVG塗り色設定。無色にSVG線色設定。
光=[16,16]へ10のSVG円描画。
フィルター光=定義にSVGフィルター作成。
SVG対象フィルター=フィルター光。
光に4でSVG画像ボカシ。
# 電球
金色にSVG塗り色設定。
[16,16]へ4のSVG円描画。

# 移動
ツリー画像へSVG描画開始。
電飾は空配列。
16回
    番号=回数-1。
    電飾[番号]=電飾シンボルをSVG参照複製。
    [20*回数,地平線+90]へ電飾[回数-1]をSVG描画移動。
ここまで。

 カラフルにしようかと思ったけど、電球部分とボカシの光部分があるので玉の時のようには出来ず、面倒になったので単色にしました💧
 色替えがないので、塗り色も最初から設定しちゃう。

アニメーション

 なでしこのプログラムではなく、SVGのアニメーションを使っていきます。
 アニメーションも、色々フクザツでむじゅかしい:persevere:
 とりあえず、変化させたい属性(attributeName)と1ループの時間(dur)とキーフレーム(values)の指定でできるようにしています。

電飾[0]の「塗り透明度」に「3s」と「0;1;0」でSVGアニメーション設定。

 こんだけで、3秒かけて透明度が変化し、点滅している感じになります☆

 電飾は飾り付けがすんでから点灯としたいので、アニメーションの設定は関数にしておいて終了時に設定することにする。

●電飾アニメーション開始
    点滅間隔=3。
    16回:
        番号=回数-1。
        電飾[番号]の「塗り透明度」に0.1をSVG属性設定。
        電飾[番号]の「塗り透明度」に「{点滅間隔}s」と「0.1;0.7;1;1;1;0.7;0.1」でSVGアニメーション設定。
        それの「begin」に「{番号%点滅間隔}s」をSVG属性設定。
ここまで。

 SVGアニメーションの仕様がよく分かってないので変化させるタイミングをキーフレームだけで無理矢理アレしていてあまり賢そうじゃない💧 keyTimesとかで出来るんですかねー?
 ともかくも、何秒かけてこの動き、というのがカンタンにできて良きです~☆

ドラッグ&ドロップ

 オーナメントがそろったので動かせるようにします。
 ツリー画像にSVGドラッグ許可して、画像内でドラッグ&ドロップの操作ができるようにします(documentとか、画像よりも広い範囲のを指定すると、画像外に要素を追い出すような操作ができるようになります)
 あとは、動かしたい要素にSVGドラッグオンするだけ! カンタン☆ よき~♪

ツリー画像のSVGドラッグ許可。
スターのSVGドラッグオン。
キャンディのSVGドラッグオン。
プレゼントのSVGドラッグオン。
長靴のSVGドラッグオン。
4回。飾玉[回数-1]のSVGドラッグオン。。。
16回。電飾[回数-1]のSVGドラッグオン。。。

制限時間内に飾り付け!

 やっぱりゲームっぽい物作りたいので、タイムリミットをもうけて飾り付けを頑張る的な感じにしようと思う。
 雪がだんだん積もっていって、雪に埋まったらオーナメントが動かせなくなっちゃう! みたいな?

雪が積もるアニメーション

 単純に60秒かけてゆっくり白い四角の高さを伸ばしていくだけ。
 積もり方が変とか言わないで💧
 

# ぼかして雪っぽくするためのフィルター
フィルター雪=定義にSVGフィルター作成。
SVG対象フィルター=フィルター雪。
5のSVG画像ボカシ。

# 描画
タイムリミット=60。# 秒
白色にSVG塗り色設定。無色にSVG線色設定。
積雪=[-10,地平線-10,画面幅+20,1]へSVG四角描画。
積雪にフィルター雪をSVGフィルター設定。
積雪アニメ=積雪の「height」に「{タイムリミット}s」と「{0};{画面高さ-地平線+20}」でSVGアニメーション設定。
積雪アニメの「repeatCount」に1をSVG属性設定。# 1回だけのアニメーション
積雪アニメの「fill」に「freeze」をSVG属性設定。# アニメーションが終わったらその状態を固定。

雪の降るアニメーション

 降ってもいないのに積もるとかあり得ないので、雪も降らせよう……などと思ったのが悲劇の始まり!

 これも、シンボルにいっこ描画して参照複製で良いかと思ったんですが……アニメーションが適用されない! 電飾はできたのに、なんでー?!:sob:
 と、まあまあハマりましたが、雪は円で描いているのでcyを変化させて上から下へ移動させようとしてましたが、よく考えたら、アニメーションはシンボルの中の円ではなく、複製されたuseにかかっているんだから変化させる属性はcyじゃなくてyでした!:joy_cat:
 ていうか最初から変形アニメーション(animateTransform)にすれば良かった。しくしくしく。

 悲劇はまだ続くのですよ。
 どうもFireFoxとchromeとでは、アニメーションの始まるタイミングが違うというかbegin属性を指定した時の挙動が違うというか……?
 フツーに考えると、beginに指定した秒数分だけ遅れてアニメーションが開始されるものだと思うんですが……

 FireFoxではまさにその通りで、希望した動作が得られるのですが、chromeでは、なんと! 実行したらみんな一斉にわーっと降ってきちゃう!! そして、beginに指定された秒数で、ぴゅっと上に戻って何食わぬ顔でアニメーションを開始(?)するんですぅ~。なにゆえに~:sob:
 これは相当ハマりましたが、気がついてSVGファイルで試したら、そんなことにはならない。
 beginの動作が違うんじゃなくて、最初に作られたアニメーション要素にbeginがない時、あるいは0sだった時の動作が違うっぽい(?)
 プラグイン自体でアニメーション設定時にindefiniteを指定して置いたら止められるけど、それだと今度はbeginを設定しない限りアニメーションが動かないことになっちゃうしなー……
 ……と、いろいろ悩んでたんですが、とりあえず「0.01s」あたりを入れとくことにした! まじか💧

メリークリスマスのアニメーション

 雪で埋め尽くされたらまた地面広すぎ感が出てくるので、ここにメリークリスマスと描画して終了ということにします。

文字

 Googleフォントから良さそげなフォントを設定。

グーグルフォント指定=ツリー画像にSVG定義領域作成。
グーグルフォント指定に「<style>@import url('https://fonts.googleapis.com/css2?family=Kaushan+Script&display=swap');</style> 」をHTML設定。

「"Kaushan Script", cursive」にSVG文字フォント設定。
「38」にSVG文字サイズ設定。
「crimson」にSVG塗り色設定。
メリークリスマス=[30,450]へ「Merry Christmas !!」をSVG文字描画。
メリークリスマスをSVG最背面。

マスク

 最初は白い四角で隠しておいて、四角を移動させることで徐々に現わす。

白色にSVG塗り色設定。無色にSVG線色設定。
文字マスク=[0,地平線,画面幅,125]へSVG四角描画。
文字マスクをSVG最背面。

# アニメーション設定
文字アニメ=文字マスクの「x」に「6s」と「0;{画面幅}」でSVGアニメーション設定。
文字アニメの「begin」に「{タイムリミット}s」をSVG属性設定。
文字アニメの「repeatCount」に1をSVG属性設定。# 1回だけのアニメーション
文字アニメの「fill」に「freeze」をSVG属性設定。# アニメーションが終わったらその状態を固定。

# アニメーション開始
(タイムリミット)秒後には、:
    メリークリスマスをSVG最前面。
    文字マスクをSVG最前面。
    8秒後には、終了処理。。。

 これは電飾と違って一回だけのループなので、発動させる時間はbeginで設定。

できました!

2022-12-23 (1).png

 スマホでも出来るようにしたつもりでしたが、思ったより動きが悪く、ちっちゃい電球を動かすのなんかめっぽう難しくてとてもムリなようです。
 マウスならちょうどイイ感じのイヤガラセだと思ったんですが:smirk:
 編集画面でタイムリミットの数値を増やせばゆっくりできるので、それでなんとか・・・(え)

おわります

 長くなった割りには、SVGのいろいろな機能もひととおり使ったハズなのに、あまり解説は出来ませんでした。
 フィルターやアニメーションなど実際に色々作ってみたら思ったようにいかず、プラグインを修正したりもあって、カレンダーのわりとはじめの段階から、クリスマスはこんなやつ、という構想を持っていたにもかかわらず、結局ギリになってしまいましたが、ともかくもなんとか間に合って、ぽいものが完成したので満足!
 ではでは、よいクリスマスを☆

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?