正多芒星

夕陽

ベランダから見る夕日。 朝日や夕日がベランダから見えるようになるのが、俺の中での冬の始まり。

しばらく前 に css の三角関数と clip-path を組み合わせて正多角形を作ってみた。 隣り合う頂点を順番に繋ぐ単純なものだったが、計算結果ではなく計算式で記述できて、自分の中の満足度は高かった。 それを何に使うのかと訊かれたくは無いけれど。

沈む夕日を見ているうちにそんなことを思い出して、その流れで思いついた。 結ぶ頂点を一つ跳ばしにしたら星型になるのではないかと。

で、早速試してみた結果がこれ。 五芒星と六芒星。

html

<div class="polygon_base"> <div class="p5"></div> </div> <div class="polygon_base"> <div class="p6"></div> </div>

css

.polygon_base { width: 200px; height: 200px; border: 1px solid black; border-radius: 50%; padding: 4px; } .polygon_base div { width: 100%; height: 100%; background: black; } .polygon_base .p5 { clip-path: polygon( calc( 50% + cos( 360deg / 5 * 0 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 5 * 0 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 5 * 2 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 5 * 2 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 5 * 4 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 5 * 4 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 5 * 1 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 5 * 1 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 5 * 3 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 5 * 3 - 90deg ) * 50% ) ); } .polygon_base .p6 { clip-path: polygon( calc( 50% + cos( 360deg / 6 * 0 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 0 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 2 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 2 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 4 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 4 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 0 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 0 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 1 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 1 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 3 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 3 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 5 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 5 - 90deg ) * 50% ), calc( 50% + cos( 360deg / 6 * 1 - 90deg ) * 50% ) calc( 50% + sin( 360deg / 6 * 1 - 90deg ) * 50% ) ); }

五芒星は、単純に一つ跳ばしで頂点を繋ぐだけ。

六芒星は、どうしようかちょっと迷って、閉じた三角形を二つ重ねてみたら上手くいった。

途中でパスが交差したり領域が重なり合ったりするが、最終的に最外周(?)となる領域でクリッピングしてくれる。 そしてクリッピングされた内側は、同じ一繋がりの領域として処理される。

五芒星と六芒星が出来ると、後は何芒星でも作れる。 頂点の数が奇数なら五芒星の、偶数なら六芒星の、単純な応用。 一つ跳ばしが二つ跳ばしや三つ跳ばしになっても、考え方は同じだからね。

ということで、前回同様ジェネレーターを作ってみた。

頂点の数と、その頂点を跳ばす数の指定に従って、12時の位置から clip-path の線を引く。

開始位置を一箇所で固定にすると、頂点の数と跳ばす数との組み合わせによって、頂点を網羅できない場合がある。 具体的には、頂点の数と跳ばす数(0始まりなので+1)が互いに素でない場合。 だから上の例の六芒星などは描けないのだが気にしない。

跳ばす数の最大値は、頂点の数の半分未満。 これは頂点の数の変更に連動させている。 跳ばす数として頂点の数の半分を超えた値を指定しても、描かれるのは半分未満を指定したパターンのどれかだからね。 無駄な指定をしなくて済む、俺に優しい設計。

以下、ソース。

html

<div class="polygon_base"> <div class="pN" id="p0"></div> </div> <div> <label> 頂点の数 <input type="number" id="vtxCnt" value="3" min="3" max="21" /> </label> <label> 跳ばす数 <input type="number" id="skpCnt" value="0" min="0" max="0" /> </label> </div>

JavaScript

window.onload = () => { const vtxElm = document.getElementById( "vtxCnt" ); const skpElm = document.getElementById( "skpCnt" ); const dsp = () => { const vtxCnt = parseInt( vtxElm.value ); const skpCnt = parseInt( skpElm.value ); const clip = ( new Array( vtxCnt ) ) .fill( "" ) .map( ( e, i ) => `calc( 50% + cos( 360deg / ${ vtxCnt } * ${ i * ( skpCnt + 1 ) } - 90deg ) * 50% ) calc( 50% + sin( 360deg / ${ vtxCnt } * ${ i * ( skpCnt + 1 ) } - 90deg ) * 50% )` ) .join( "," ); document.getElementById( "p0" ).style.clipPath = `polygon( ${ clip } )`; }; const modSkp = () => { const curCnt = parseInt( skpElm.value ); const maxCnt = Math.floor( ( parseInt( vtxElm.value ) - 1 ) / 2 ) - 1; skpElm.max = maxCnt; skpElm.value = curCnt > maxCnt ? maxCnt : curCnt; } vtxElm.addEventListener( 'change', () => { modSkp(); dsp(); }, false ); skpElm.addEventListener( 'change', () => { dsp(); }, false ); }

そして今回も、これが何に使えるのかなんて考えない。