HTMLコード
<body>
<canvas id="can"></canvas>
<script src="example.js"></script>
</body>
上記コードのように、HTMLで「canvas要素」をID名「can」で記述し、
それをJavascript(ファイル名「example.js」)でいじっていきたいときの、
canvasについてのお話です。
サイズ
まずキャンバスのサイズについては
「canvasサイズについて」
を参照してください。
今回キャンバスサイズは500×500にしています。
次に実際にキャンバスへ描画していくのですが、
「今から2Dで絵を描いていきますよ〜」とPCに伝えないといけません。
要素の取得
JSコード
let can=document.getElementById("can");
let con=can.getContext("2d");
ということでまず「getElementById」でHTMLの「canvas要素」を取得し、
先程のPCへ伝えるのは、この「getContext("2d")」で2Dの描画を作成していくというのを決めています。
今後描画をする記述を行う際は、このコンテキストを定義した変数「con」を経由して記述していく必要があります。
四角く塗りつぶし
JSコード
con.fillStyle="red";
con.rect(10,20,200,100);
con.fill();
まずは四角の塗りつぶしを描画していきます。このコードのように、
「fillStyle」で塗りつぶしの色を指定しておいて(これだけだと表示はされない)、
「rect」で「横座標,縦座標,横サイズ,縦サイズ」を指定(この段階でも表示はされない)、
最後に「fill();」(関数の呼び出しの方法に似てますね)で
「fillStyle」を「rect」の数値を基準として描画します。
こんな感じになりました。
ちなみに、「rect」でのサイズ指定の際、キャンバスサイズより大きいサイズは、キャンバスサイズからはみ出すため表示されません。
なのでキャンバスサイズが500に対し、rectサイズが8000とか意味わからん数値でも、
500までしか表示されないということです。
JSコード
con.fillStyle="red";
con.fillRect(10,20,200,100);
また、「rect」と「fill」を一緒にかく「fillRect」でも
同じ結果が出ます。
ただこれは「rect」で指定したような「横座標,縦座標,横サイズ,縦サイズ」を指定するものなので、
四角を作る際に使用する感じになりますね。
四角く枠線
次にこの塗りつぶしに対して枠線を作っていきます。
JSコード
con.fillStyle="red";
con.strokeStyle = "black"; //追加
con.rect(10,20,200,100);
con.fill();
con.stroke(); //追加
「strokeStyle」で枠線の色を指定し(これだけだと枠線は表示されない)、
「stroke();」で「strokeStyle」を「rect」の数値を基準として描画します。
こんな感じになりました。
JSコード
con.strokeStyle = "black";
con.strokeRect(10,20,200,100);
ちなみに先程の「fillRect」と考え方は一緒で、
「strokeRect」を使用すると四角枠が出来上がります。
点線
JSコード
con.fillStyle="red";
con.strokeStyle = "black";
con.lineWidth = 5;
con.setLineDash( [ 4 ] ); //追加
con.rect(10,20,200,100);
con.fill();
con.stroke();
枠線を画像のように点線にしたい場合、
「setLineDash」を使用して、値を[]で囲み、今回だったら「4」を入れることで、
黒点が4px、間が4pxの点線になりました。
線の太さ
JSコード
con.fillStyle="red";
con.strokeStyle = "black";
con.lineWidth = 5; //追加
con.rect(10,20,200,100);
con.fill();
con.stroke();
先程の記述だと枠線の幅は
「canvasAPI」のコンテキストが元々持っている枠線幅である1px
(↑サイズ指定してない時のデフォルト「縦150」「横300」も元々持っているもの)
になってしまうため、
上記コードのように「lineWidth」で枠線幅を指定できます。
指定後に「stroke();」をすると、この枠線幅も考慮した枠線になります。
このように枠線がぶっとくなりましたね。
今回の記述は「塗りつぶし」と「枠線」の色と「枠線幅」を指定して
それを同一の「rect」の数値に当てはめて
「塗りつぶし」と「枠線」それぞれを描画しているというものでした。
パスを使用しての描画
次にキャンバス内に「パス」を登録して線を描いていきます。
ちなみにここでの「パス」とは、開始点と終了点みたいなもので、
今回は一本線で左下から右上に伸びる線を描いていきます。
JSコード
con.strokeStyle = "red";
con.lineWidth = 5;
con.moveTo(30,100);
con.lineTo(90,40);
con.stroke();
先程枠線を描画した時にも出てきた「strokeStyle」「lineWidth」が今回も出てきましたね。
これらは「枠線」に対してのみのコードではなく、
「線」全般に対して使用することができます。
枠線も線の一種ですしね。
てことでまずこれら2つを記述し線の「色」と「太さ」を指定、
「moveTo」で描きたい線の開始点座標を指定します。
((30,100)と指定しているのはX軸方向(横方向)に「30」、Y軸方向(縦方向)に「100」の位置)
次に「lineTo」で線の終了点座標を指定します。
ここまでの記述のみだと開始点と終了点しか指定していないため、
これらの点を「strokeStyle」「lineWidth」で決めた線でつなげるために「stroke」を記述しています。
こんな感じの線になりましたね。
次はこの線にさらに線を付け足して「へ」の字を描いていきます。
JSコード
con.strokeStyle = "red";
con.lineWidth = 5;
con.moveTo(30,100);
con.lineTo(90,40);
con.lineTo(180,140); //追加
con.stroke();
このように「lineTo」の後に「lineTo」で違う座標を指定して「stroke」で線を描いてやると、
「moveTo」座標と1つめの「lineTo」座標を線で繋いだ後に、
1つめの「lineTo」座標と2つめの「lineTo」座標も続いて繋いでくれます。
このような感じになりましたね。
そして「moveTo」座標と2つめの「lineTo」座標にも線をつなげたい時は、
JSコード
con.strokeStyle = "red";
con.lineWidth = 5;
con.moveTo(30,100);
con.lineTo(90,40);
con.lineTo(180,140);
con.closePath(); //追加
con.stroke();
「今まで連結して描いていたパスを閉じますよ〜」ということで、
「closePath」を記述することで三角形の図形を描くことができました。
こんな感じですね。
ちなみにこの図形を塗りつぶしたい時は、
JSコード
con.strokeStyle = "red";
con.fillStyle="blue"; //追加
con.lineWidth = 20;
con.moveTo(30,100);
con.lineTo(90,40);
con.lineTo(180,140);
con.closePath();
con.stroke();
con.fill(); //追加
このように先程も使った「fillStyle」で「色」を指定し、
「fill」で指定した色で塗りつぶします。
パスにより塗りつぶす座標が指定されているため、
前回のように「rect」で座標や大きさを指定する必要はないということですね。
こんな感じに塗りつぶせました。
「ちょっと赤線が細くなってない?」って思いませんか?
そうです細くなっています。その理由は、
線を描くとき、今回だと20pxの「lineWidth(太さ)」で指定して描いていますが、
この20pxは開始点や終了点を中心として10pxずつ広がって20pxとなっています。
対して「fill」により塗りつぶした図形は繋いだパス座標内を全て塗りつぶします。
「stroke」の後に「fill」を記述すると、JSコードは上から順に読み込むため、
「stroke」の内側10px線部分が消えてしまうんですね。
JSコード
con.strokeStyle = "red";
con.fillStyle="blue";
con.lineWidth = 20;
con.moveTo(30,100);
con.lineTo(90,40);
con.lineTo(180,140);
con.closePath();
con.fill(); //入れ替え
con.stroke(); //入れ替え
このように、「fill」の後に「stroke」を記述してあげると、
画像のような20px赤線内の青塗りつぶしが完成しました。
線端の描画
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
次は画像のような「V」の字をパスを登録して記述してみました。
まずは線端を変えていきましょう。
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.lineCap = "round"; //追加
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
線端を変更するには「lineCap」を使用します。
デフォルトは「butt」で先程までの角張った線端なのですが、
「round」にすることによって
こんな感じの丸っこいものになります。
線の接触地点の描画
次は線が接している部分です。
この「V」の字、線端が角張っていて、また線が接している部分は
画像の黒線部分が設定してもいないのに勝手に塗りつぶされてしまっています。
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.lineCap = "round";
con.lineJoin = "bevel"; //追加
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
「lineJoin」を使用し、先程までのとんがったデフォルトは「miter」、
それを「bevel」にすることで
線の角と角を繋いだ線を塗りつぶすような感じになります。
今回は丸っこくしたいので、
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.lineCap = "round";
con.lineJoin = "round"; //追加
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
「lineJoin」を「round」にすることで接触部分も丸くできました。
シャドウ
次は、描画したものに影をつけていきます。
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.lineCap = "round";
con.lineJoin = "round";
con.shadowOffsetX = 5; //追加
con.shadowOffsetY = 10; //追加
con.shadowColor = "blue"; //追加
con.shadowBlur = 10; //追加
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
「shadowOffsetX」で影の横方向を指定し、
「shadowOffsetY」で影の縦方向を指定、
「shadowColor」で影の色を指定し、
「shadowBlur」では影をぼかす(ブラー)ことをしています。
すると画像のように右下方向に少しボケた青色影ができましたね。
不透明度
次は不透明度の指定です。
不透明度は線や塗りつぶしの色があるときは「rgba」の「a」の値を1以下にしてもいいのですが、
後でやるような色部分を画像で表示させるような場合には「rgba」は使用できません。
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.globalAlpha = 0.5; //追加
con.lineCap = "round";
con.lineJoin = "round";
con.shadowOffsetX = 5;
con.shadowOffsetY = 10;
con.shadowColor = "blue";
con.shadowBlur = 10;
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
そんな時は「globalAlpha」を使用し、値は「0」が「完全な透明」、「1」が「完全な非透明」で、
0〜1の間で指定します。
今回は「0.5」と記載したので、画像のように半透明な感じになりました。
クリア
JSコード
con.strokeStyle = "red";
con.lineWidth = 20;
con.globalAlpha = 0.5;
con.lineCap = "round";
con.lineJoin = "round";
con.shadowOffsetX = 5;
con.shadowOffsetY = 10;
con.shadowColor = "blue";
con.shadowBlur = 10;
con.moveTo(30,30);
con.lineTo(60,140);
con.lineTo(90,30);
con.stroke();
con.clearRect( 0 , 0 , 60 , can.clientHeight); //追加
色々記述して図形を表示させていましたが、
途中で一度キャンバス内を透明に塗りつぶし(何も書かれてないように見える)をしたい時や、
部分だけ見えないようにするには
「clearRect」で「横座標」「縦座標」「横幅」「縦高さ」で記述してやると
指定した箇所を見えなくできます。
今回は「V」の折り返し地点である、X座標が60pxのところで設定しました。
すると、画像のように縦半分になりましたね。
パスのリセット
次は線によって色が違う「V」の字を描いていきます。
JSコード
con.strokeStyle = "red";
con.lineWidth = 10;
con.lineCap = "round";
con.lineJoin = "round";
con.moveTo(30,30);
con.lineTo(60,100);
con.stroke();
con.beginPath();
con.strokeStyle = "blue";
con.moveTo(60,100);
con.lineTo(90,30);
con.stroke();
このコードは、最初赤の線のパスを指定して線を描画、
一旦パスをリセットするために「beginPath」を記述し、
再度次は青の線のパスを指定して描画しています。
円弧
次は「stroke」を使用して、直線ではなく円を描いていきます。
JSコード
// 1番
con.lineWidth = 5;
con.strokeStyle = "red";
con.arc(100 , 100 , 50 , 0 , Math.PI*2 );
con.stroke();
con.beginPath();
// 2番
con.lineWidth = 5;
con.strokeStyle = "red";
con.arc(250 , 100 , 60 , 0 , Math.PI*1 );
con.stroke();
con.beginPath();
// 3番
con.lineWidth = 5;
con.strokeStyle = "red";
con.arc(400 , 100 , 50 , 0 , Math.PI/2 );
con.stroke();
con.beginPath();
// 4番
con.lineWidth = 5;
con.strokeStyle = "red";
con.arc(550 , 100 , 50 , 0 , Math.PI/2 , true);
con.stroke();
「stroke」を使って円を描くには、先程もやった「lineWidth」で線の太さを、「strokeStyle」で色を指定し、
「arc」を使用して「円の中心の横座標,中心の縦座標,半径(px),円の開始地点,終了地点,方向」を指定しています。
「円の中心の横座」「円の中心の縦座標」で、円の中心位置を決定し、「半径」で円の大きさを決めます。
「円の開始、終了地点」は「Math.PI」で記述し、
「0」が時計で言うところの「3時」の方向、
「Math.PI/2」は「6時」(「*0.5」でも同じ意味になります。)
「Math.PI*1」は「9時」、
「Math.PI*1.5」は「12時」
「Math.PI*2」は一周回って「3時」を意味します。
要するに「Math.PI」が1の時「180度」、2の時は「360度」と言うことです。
この開始点から終了点までを円状に線(stroke)が通ることになり、
この線が通る「方向」を「true」か「false」で指定します。
「true」を指定すると反時計回り、「false」だと時計回りになります。
何も指定しなければデフォルトで「false」になり、時計回りになると言うことですね。
線の角を円弧に変更
JSコード
con.strokeStyle = "red";
con.moveTo( 30 , 100 ); //A
con.lineTo( 90 , 40 ); //B
con.arcTo( 110 , 20 , 190 , 100 , 20); //C
con.lineTo( 190 , 100 ); //D
con.stroke();
次は画像のように直線と直線の接点部分を角から円弧に変える方法です。
まず「moveTo」と「lineTo」を使用して前回のように直線を2本書いていきます。
普通に角の直線を描くときは、A→C→Dでパスを登録して「stroke」で描きますが、
角を円弧にしたいところ(今回だと1つ目の「lineTo」の後)に「arcTo」を使用します。
「arcTo」の()には「C地点横座標,C地点縦座標,D地点横座標,D地点縦座標,半径」を記述し、
「arcTo」直前の「lineTo」は、C地点から少しA地点よりの位置に座標を記述します。
大体は「arcTo」の「半径」の値を、C地点座標からA地点座標に少し戻すために、B地点座標の値に+ーするといい感じになりました。
すると画像でもわかるように、B地点から線が湾曲し出し、設定した円弧を線が通って、円弧とCD線が合流するところでまた直線になり、D地点で線が終了するといったものになります。
楕円形
次は楕円形を描いていきます。
JSコード
// 1番
con.lineWidth = 5;
con.strokeStyle = "red";
con.ellipse(100, 150, 80, 30, 0 , 0, Math.PI*2 );
con.stroke();
con.beginPath();
// 2番
con.lineWidth = 5;
con.strokeStyle = "red";
con.ellipse(240, 150, 50, 60, 0 , 0, Math.PI*2 );
con.stroke();
con.beginPath();
// 3番
con.lineWidth = 5;
con.strokeStyle = "red";
con.ellipse(350, 150, 80, 30, Math.PI/3, 0, Math.PI*2 );
con.stroke();
con.beginPath();
// 4番
con.lineWidth = 5;
con.strokeStyle = "red";
con.ellipse(480, 150, 80, 30, 0 , 0, Math.PI*1.5 );
con.stroke();
con.beginPath();
// 5番
con.lineWidth = 5;
con.strokeStyle = "red";
con.ellipse(570, 150, 80, 30, 0 , 0, Math.PI*1.5, true );
con.stroke();
円を描いた時と似ていますが、「lineWidth」と「strokeStyle」で線幅と色を指定し、
「ellipse」の()内は「楕円中心横座標,楕円中心縦座標,楕円横半径,楕円縦半径,楕円の回転,楕円の開始地点,終了地点,方向」をそれぞれ記述します。
「楕円中心横座標」「楕円中心縦座標」で楕円のキャンバス内における位置を指定し、
「楕円横半径」「楕円縦半径」で楕円の大きさと形を指定、
「楕円の回転」では円でも使用した「0」か「Math.PI」を使用して楕円を回転させ、
「楕円の開始地点」「終了地点」で、「円」で指定したように「0」か「Math.PI」を使用して線が通る道を指定、
「方向」ではこれも「円」でのように、線が通る方向を「true:反時計回り」「false:時計回り」で指定します。
「円」での記述に少し内容が増えた感じですね。
例コードに1〜5まで色々な楕円を記述してますので、参考にしてください。
2次べジェ曲線
次は、2次べジェ曲線を描いていきます。
そもそもべジェ曲線とは、開始点や終了点、制御点などのパスを登録し、それらを使用して滑らかな曲線を描く方法です。
2次べジェ曲線とは開始点と終了点を除く、制御点が1つの曲線を言います。
後にやる3次ベジェ曲線とは制御点が2つのものですね。
それでは2次ベジェ曲線のコードから見ていきます。
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.quadraticCurveTo(60 , 100 , 150 , 50);
con.stroke();
このような曲線のように、割と自由度の高い曲線を描くことができます。
このベジェ曲線を使用することで、手書きでは描くことができない滑らかな曲線を描くことができます。
2次ベジェ曲線で使用するパスは、「開始点」「制御点」「終了点」の3つになり、
「開始点」と「終了点」を結ぶ線を「制御点」により曲げていきます。
この画像は、上記ベジェコードに記述の3つのパスを表示しています。
「moveTo」で開始点のパスを指定し(横座標50,縦座標50)、
「quadraticCurveTo」の()内では「制御点横座標60,制御点縦座標100,終了点横座標150,終了点縦座標50」のパスを指定、
それらを使用した線を描くため「stroke」を使用します。
ベジェ曲線ではこの「制御点」が大切で、例えば、
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.quadraticCurveTo(60 , 200 , 150 , 50);
con.stroke();
上記コードのように制御点を離してやればやるほど、曲線の曲がり具合は強くなり、
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.quadraticCurveTo(120 , 70 , 150 , 50);
con.stroke();
上記コードのように制御点を近づけるほど、曲線の曲がり具合は緩くなります。
また、制御点を開始点か終了点に近づけるほど、近づけた側に曲線の曲がり部分が寄っているのがわかるかと思います。
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.fillStyle="blue"; //追加
con.moveTo( 50 , 50 );
con.quadraticCurveTo(120 , 70 , 150 , 50);
con.stroke();
con.fill(); //追加
このように「塗りつぶし」を追加してやると、開始点と終了点と曲線を囲った塗りつぶしが可能になります。
3次べジェ曲線
次は3次べジェ曲線をやっていきます。
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.bezierCurveTo(70 , 100 , 120 , 100 , 150 , 50);
con.stroke();
3次ベジェ曲線で使用するパスは、「開始点」「制御点✖︎2」「終了点」の4つになり、
「開始点」と「終了点」を結ぶ線を、2つの「制御点」により曲げていきます。
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.bezierCurveTo(70 , 100 , 120 , 100 , 150 , 50);
con.stroke();
この画像は、上記ベジェコードに記述の4つのパスを表示しています。
「moveTo」で開始点のパスを指定し(横座標50,縦座標50)、
「bezierCurveTo」の()内では「1制御点横座標70,1制御点縦座標100,2制御点横座標120,2制御点縦座標100,終了点横座標150,終了点縦座標50」のパスを指定、
それらを使用した線を描くため「stroke」を使用します。
まずは1制御点のパス(座標)をもっと下に持って行ってみます。
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.bezierCurveTo(70 , 180 , 120 , 100 , 150 , 50);
con.stroke();
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 50 , 50 );
con.bezierCurveTo(70 , 180 , 210 , 100 , 150 , 50);
con.stroke();
上記コードのように2制御点のパスを終了点より右側へ持っていくと、終了点から膨らんだ曲線になったりします。
これがベジェの特徴で、制御点に引っ張られる形で曲線の曲がり具合が調整されてしまいます。
べジェ曲線でハートマーク
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 150 , 150 );
con.quadraticCurveTo(220 , 110 , 220 , 70 );
con.stroke();
con.beginPath();
con.strokeStyle = "blue";
con.moveTo( 220 , 70 );
con.bezierCurveTo(220 , 30 , 170 , 30 , 150 , 70);
con.stroke();
con.beginPath();
con.strokeStyle = "Black";
con.moveTo( 150 , 70 );
con.bezierCurveTo(130 , 30 , 80 , 30 , 80 , 70);
con.stroke();
con.beginPath();
con.strokeStyle = "yellow";
con.moveTo( 80 , 70 );
con.quadraticCurveTo(80 , 110 , 150 , 150 );
con.stroke();
2次ベジェと3次ベジェを組み合わせると、画像のようなハートマークを描くこともできます。
今回はどこのコードが何を描いているかわかるように、
それぞれのベジェ記述を「beginPath」で一度リセット、色を変更しています。
その関係で毎回「moveTo」を直前のベジェ終了点で記述して描画していますが、
JSコード
con.lineWidth = 5;
con.strokeStyle = "red";
con.moveTo( 150 , 150 );
con.quadraticCurveTo(220 , 110 , 220 , 70 );
con.bezierCurveTo(220 , 30 , 170 , 30 , 150 , 70);
con.bezierCurveTo(130 , 30 , 80 , 30 , 80 , 70);
con.quadraticCurveTo(80 , 110 , 150 , 150 );
con.closePath();
con.stroke();
このコードのように連続してベジェを記述することで、
一本の線が「moveTo」の開始点から「quadraticCurveTo」「bezierCurveTo」「bezierCurveTo」「quadraticCurveTo」の終了点を通って「moveTo」に戻ってくる(「closePath」)と言う描画ができます。
設定の保存・復元
JSコード
con.save();
con.fillStyle = "green";
con.fillRect(10,10,90,90);
con.restore();
次は描画設定の保存と復元の方法と説明です。
「save」を使うことで、「save」より前の描画設定を保存することができ、
「restore」で、「save」により保存した設定を復元することができます。
上記コードは「save」でそれより前(今回は何もない)の設定を保存し、
その後「色」「座標」「大きさ」を指定した「塗りつぶし」を描画、
「restore」で「save」により保存した設定を復元することで、設定を初期化していると言うことになります。
そしてこの「save」はどんどんストックすることができ、「restore」によりストックした「save」を順番に復元できる特徴があります。
このコードでは1つずつしか記述しておらず、描画されるものに違いが出ず、いまいち分かりにくいので、
新しくコードを記述していきます。
JSコード
con.save(); //1
con.fillStyle = "red";
con.save(); //2
con.fillStyle = "blue";
con.globalAlpha = 0.5;
con.save(); //3
con.fillStyle = "green";
con.fillRect(10,10,90,90);
con.restore(); //3
con.fillRect(110,10,90,90);
con.restore(); //2
con.fillRect(210,10,90,90);
con.restore(); //1
con.fillRect(310,10,90,90);
今回は「save」を3回、それに合わせて「restore」も3回使っています。
順を追って説明すると、
1の「save」で何も指定されていない状態(デフォルトの色は黒になる)の設定を保存、
2の「save」でそれより前にある「red」の色を保存、
3の「save」で色設定「blue」と、「不透明度0.5」を保存、
その後に色設定「green」を記述しました。
次に、「fillRect」により四角塗り潰しを描画、この時は最後に色設定した「green」と、「不透明度0.5」が適用されます。
次に3の「restore」により3の「save」設定を復元、次の行で「fillRect」をすることで、3の「save」より前の「blue」「不透明度0.5」設定で四角を描画、
2の「restore」では2「save」以前の設定なので、「red」で描画します。この時「globalAlpha」は2「save」より後なので、「globalAlpha」の適用はなくなっています。
そして最後の1の「restore」により1の「save」以前の設定を復元、いわゆる初期化状態になり、
ここで「fillRect」をすることにより、初期値である「black」が適用されました。
このように、保存と復元を何回もすることで、以前の保存設定までさかのぼって設定を適用できます。
なお、この保存と復元は、「描画設定(色とか不透明度等)」に対してのものなので、「パス」に対しては効果がありません。
なので「fillRect」や「moveTo」といったパスを登録するメソッドを保存することができないと言うことです。
パスをリセットするときは「beginPath」を使用しましょう。
文字の描画
JSコード
con.font = "30px serif";
con.fillStyle = "red";
con.fillText( "tagotyan" , 10 , 30 );
文字を描画するとき、「fillText」メソッドを使用し、(表示文字,横座標,縦座標,最大幅)でそれぞれ指定して描画します。
「最大幅」は省略することができ、その場合は文字の横幅が伸縮することなく表示されます。
フォントの初期値は「10px(フォントサイズ) sans-serif(フォント種類)」であり、
「fillText」の前に「font」で"(フォントサイズ)(フォント種類)"を選択することで文字の大きさと種類を変更することができます。
おなじみ「fillStyle」で文字の色を変更できます(「fontStyle」ではない点に注意)。
先程の「最大幅」について少し説明です。
JSコード
con.font = "30px serif";
con.fillStyle = "red";
con.fillText( "tagotyan" , 10 , 30 , 60 );
今回1文字は30pxあり、「tagotyan」は合計8文字、と言うことは、この文字列を表示させる場合240pxの横幅が必要と言うことになります。
そんなとき「最大幅」の設定(今回だと60px)をこの文字列横幅(今回だと240px)より小さくすると、
画像のように横幅が縮小された状態で表示されます。
文字の枠線の描画
JSコード
con.font = "30px serif";
con.strokeStyle = "red";
con.strokeText( "tagotyan" , 10 , 30 );
文字の枠線は、文字の表示を「stroke」にした感じです。
「fillStyle」→「strokeStyle」に、「fillText」→「strokeText」に変わっていることがわかるかと思います。
文字の水平基準設定
JSコード
// 縦基準線
con.moveTo( 100 , 0 );
con.lineTo( 100 , 200 );
con.stroke();
// 水平基準設定
con.font = "30px serif";
con.textAlign = "left";
con.fillText( "tago" , 100 , 30 ); //left
con.textAlign = "right";
con.fillText( "tago" , 100 , 60 ); //right
con.textAlign = "center";
con.fillText( "tago" , 100 , 90 ); //center
con.textAlign = "start";
con.fillText( "tago" , 100 , 120 ); //start
con.textAlign = "end";
con.fillText( "tago" , 100 , 150 ); //end
今回はわかりやすくするために、for文とかを使用せず一つずつ記述しました。
この「textAlign」と言うのは、指定した座標に対して、文字がはじまる基準をどこに置くかを設定できます。
設定の種類は「left,right,center,start,end」から選ぶことができ、それぞれ画像のように表示されます。
文字の垂直基準設定
JSコード
// 縦基準線
con.moveTo( 0 , 70 );
con.lineTo( 400 , 70 );
con.stroke();
// 水平基準設定
con.font = "20px serif";
con.textBaseline = "top";
con.fillText( "タgo" , 0 , 70 ); //top
con.textBaseline = "hanging";
con.fillText( "タgo" , 50 , 70 ); //hanging
con.textBaseline = "middle";
con.fillText( "タgo" , 100 , 70 ); //middle
con.textBaseline = "alphabetic";
con.fillText( "タgo" , 150 , 70 ); //alphabetic
con.textBaseline = "ideographic";
con.fillText( "タgo" , 200 , 70 ); //ideographic
con.textBaseline = "bottom";
con.fillText( "タgo" , 250 , 70 ); //bottom
この「textBaseline」は、指定した座標に対して、文字の高さをどこ基準で表示させるかを設定できます。
設定の種類は「top,hanging,middle,alphabetic,ideographic,bottom」から選ぶことができ、それぞれ画像のように表示されます。
「top」と「hanging」は基準が文字より上に表示されますが、「hanging」の方が少し文字に被ります。
「alphabetic」はアルファベット基準で基準を文字下に、
「ideographic」は漢字基準で基準を文字下にしてくれます。
「ideographic」と「bottom」は画像のように一緒の感じですね。
座標の変換
指定した座標を移動させたり、回転、拡大、縮小、変換させるためには、
変換バッファと言うところにあらかじめ「translate(移動)」や「rotate(回転)」、「scale(拡大縮小)」、「transform(変換)」等とその数値を入れておいて、
その後から記述するパスに対して、変換バッファを通した上でのパス登録になります。
この変換バッファは描画設定にあたるので、
先程やった「save」と「restore」によりリセットできます。
座標の移動
ますは「translate」メソッドから説明していきます。
キャンバスで描画する際にパス(座標)を登録するとき、
キャンバスの左上が「0,0」の初期値をベースに座標を記述していきます。
「translate」と言うのはこの初期値を変更することができます。
JSコード
const can=document.getElementById("can");
const con=can.getContext("2d");
con.translate(can.width/2,can.height/2);
上記コードは400px四方のキャンバスに対し、座標初期値を縦横2分の1ずつの箇所(200,200)への変更を変換バッファへ入れています。
なので画像の通り、元々初期値(0,0)であったところは座標(-200,-200)になり、
キャンバスの中央が初期値座標(0,0)になり、
この「translate」以後はパスを記述するとき基準を新しい初期値で登録しないといけません。
回転
次は描画したものを回転させる方法です。
JSコード
con.translate(can.width/2,can.height/2);
con.lineWidth = 10;
con.strokeStyle = "red";
con.moveTo( 5 , 0 );
con.lineTo( 100 , 0 );
con.stroke();
まず先程の「translate」を使用して座標をキャンバスの中心へ、
そこから簡単に横に伸びる直線を描いてみました。
この横の直線を回転(今回は90度回転させる)させていきます。
JSコード
con.translate(can.width/2,can.height/2);
con.rotate(90 * Math.PI / 180);
con.lineWidth = 10;
con.strokeStyle = "red";
con.moveTo( 5 , 0 );
con.lineTo( 100 , 0 );
con.stroke();
「rotate」を変換バッファに入れると、それ以降のパスはキャンバスが回転した状態で表示されます。
「rotate」の()内の記述方法は「角度 * Math.PI / 180」で表示させることができ、
今回90度右回りに回転させたかったため、「角度」の場所に「90」を記述しています。
当然ここに「45」を記述すると
このように斜め45度の線になりましたね。
拡大、縮小
次は座標の拡大と縮小をやっていきます。
JSコード
con.fillStyle = "red";
con.fillRect(10,10,10,10);
とりあえずチビ四角を描画しました。
ここから「scale」メソッドを使用して拡大していきます。
JSコード
con.fillStyle = "red";
con.fillRect(10,10,10,10);
con.scale(2,2); //A
con.fillRect(10,10,10,10);
con.scale(3,5); //B
con.fillRect(10,10,10,10);
「scale」の()内は「横方向座標の倍率,縦方向座標の倍率」を表し、その値を変換バッファへと入れています。
Aでは縦横どちらも2倍なので、
次の「fillRect」は(20,20,20,20)になります。
Bでは元々変換バッファに縦横どちらも2倍が入っている状態なので、
横倍率は「2(A)*3(B)=6倍」、縦倍率は「2(A)*5(B)=10倍」になります。
なのでBの次の「fillRect」は(60,100,60,100)になります。
縮小の場合は「scale」の値を小数点以下(0.5とか)にすれば、倍率が0.5倍になってくれます。
このように拡大縮小はパス全部にかかるので、座標と大きさ両方が倍化されます。
「transform」で座標の変換
次は、「座標変換マトリクス」と言うものを使用して、「transform」メソッドを使って座標を変更していきます。
JSコード
con.fillStyle = "red";
con.transform(1,0,0,1,0,0);
con.fillRect(0,0,100,100);
上記コードは、「transform」メソッドを初期値で記述した後、
「fillRect」で真四角の赤正方形を描画しています。
初期値なので変形せずにいるこの四角を、「transform」の値を変更することで変換していきます。
「transform」の()内の値は、「A,B,C,D,E,F」
A 水平スケール(幅)
B 垂直方向の傾斜
C 水平方向の傾斜
D 垂直スケール(高さ)
E 横方向の移動
F 縦方向の移動
を意味しています。
「大きさ」を意味する「A」「D」は初期値が「1」で、
その他は「0」になりますね。
では一つずつ試していってみます。
JSコード
con.fillStyle = "red";
con.transform(3,0,0,1,0,0);
con.fillRect(0,0,100,100);
まず「A」を「1→3」に変えてみました。「水平スケール(幅)」を意味するので横に大きくなりましたね。
JSコード
con.fillStyle = "red";
con.transform(1,1,0,1,0,0);
con.fillRect(0,0,100,100);
次に「B」を「0→1」に変えてみました。「垂直方向の傾斜」を意味するので、座標が垂直方向(縦方向)にずれて下がっているのがわかります。
JSコード
con.fillStyle = "red";
con.transform(1,0,1,1,0,0);
con.fillRect(0,0,100,100);
次に「C」を「0→1」に変えてみました。「水平方向の傾斜」を意味するので、座標が水平方向(横方向)にずれて右にいっています。
JSコード
con.fillStyle = "red";
con.transform(1,0,0,2,0,0);
con.fillRect(0,0,100,100);
次は「D」を「1→2」に変えてみました。「垂直スケール(高さ)」を意味するので縦に大きくなりました。
JSコード
con.fillStyle = "red";
con.setLineDash([4]); //わかりやすいように元の位置を点線で描画
con.strokeRect(0,0,100,100); //わかりやすいように元の位置を点線で描画
con.transform(1,0,0,1,0,20);
con.fillRect(0,0,100,100);
次は「E」を「0→20」に変えてみました。「横方向の移動」を意味するので、四角自体(座標全体)が横へ移動しました。
JSコード
con.fillStyle = "red";
con.setLineDash([4]);
con.strokeRect(0,0,100,100);
con.transform(1,0,0,1,0,20);
con.fillRect(0,0,100,100);
最後は「F」を「0→20」に変えてみました。「縦方向の移動」を意味するので、四角自体(座標全体)が縦へ移動しました。
このように6つの値にはそれぞれ意味があり、この値の意味をしっかりと理解することで「transform」を理解することができます。
では少し応用をやってみます。
JSコード
// 「TAGO」を文字で描画
con.font = "40px serif";
con.fillStyle = "red";
con.fillText("TAGO",0,0);
// 「TAGO」の下に境界線を描画
con.moveTo(0,0);
con.lineTo(120,0);
con.stroke();
// 「transform」で座標を反転
con.transform(1,0,0,-1,0,0);
// 半透明化
con.globalAlpha = 0.3;
// 半透明の文字を描画
con.fillText("TAGO",0,0);
上記コードは、「transform」の「垂直スケール(高さ)」をマイナス値にすることで座標を縦向きに反転させ、
半透明にした文字を描画しています。
こうすることで水面鏡のように描くことができます。
「setTransform」で座標の再変換
「transform①」で座標を変換した後、もう一度「transform②」を記述すると、
「transform①」の座標状態の上に「transform②」が追加される仕組みになってます。
(translateやrotateと一緒ですね)
この「transform①」を一度初期化した上で「transform②」を適用させるには
今までのやり方だと「save」「restore」を使用しますが、
「setTransform」メソッドを使用すると今までの座標変換が全て初期化されます。
JSコード
con.translate(120,0);
con.fillStyle = "red";
con.transform(2,0,0,1,0,0);
con.fillRect(0,0,100,100);
上記コードは「translate」で座標を120px右へ移動した後、
「transform」で「水平スケール(幅)」の値を増やして横方向に四角を伸ばしています。
では「setTransform」を使用してみます。
JSコード
con.translate(120,0);
con.fillStyle = "red";
con.transform(2,0,0,1,0,0);
con.fillRect(0,0,100,100);
con.fillStyle = "blue";
con.setTransform(1,0,0,2,0,0);
con.fillRect(0,0,100,100);
このように、「translate」での座標移動と「transform」での「水平スケール」の変形が初期化され、
「setTransform」の「垂直スケール(高さ)」のみが変形していることがわかります。
また、「fillStyle」等の変換メソッド以外のものは初期化されないこともわかるかと思います。
なので変換メソッドをただ単に初期化のみを行う場合は、
「save()」「restore()」での初期化と
「setTransform(1,0,0,1,0,0)」での初期化
があるということですね。
画像の描画
画像ファイルを描画する時は
まず事前に画像ファイルをJSに読み込んでから使用しないといけません。
(画像サイズは「128 × 85」のものを使用)
JSコード①
const imgOb = new Image();
imgOb.src = "画像URL";
このように、「Image」オブジェクトを「imgOb」としてインスタンス化し、
このインスタンス化した「Image」オブジェクトに画像のURLを与えています。
これで画像ファイルをJSに読み込めました。
次に「drawImage」メソッドで画像を描画していくのですが、
JSコード②
const imgOb = new Image();
imgOb.src = "画像URL";
con.drawImage( imgOb , 30 , 30 );
このように記述しても何も描画されません。
これは、コード①で画像を読み込みが完了する前に「drawImage」で描画しようとしてしまっているからです。
よって画像を読み込む前に描画しないようにするために、load イベントを使用して画像をロードしてから描画をする必要があります。
JSコード
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
con.drawImage( imgOb , 30 , 30 );
});
このように何らかのイベントを取得できる「addEventListener」で、
画面がロードした時というイベントを取得する「load」を使用、
ロードした後にする処理とした上で
「drawImage」で画像を描画すると
このように描画することができましたね。
この「drawImage」メソッドは、引数である()内を「3つ」「5つ」「9つ」にすることでそれぞれ意味が変わってきます。
3つの場合(上記コード)、Canvasの指定した位置に画像を描画
「image , x , y」
image: 貼り付けるイメージ
x: 貼り付け先のキャンバス座標x(座標はデフォルトでは画像の左上基準)
y: 貼り付け先のキャンバス座標y
5つの場合、Canvasの指定した位置に拡大・縮小して画像を描画
「image x , y , w , h」
image: 貼り付けるイメージ
x: 貼り付け先のキャンバス座標x
y: 貼り付け先のキャンバス座標y
w: 貼り付け画像の幅
h: 貼り付け画像の高さ
JSコード
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
con.drawImage( imgOb , 30 , 30 , imgOb.width*2 , imgOb.height );
});
9つの場合、元画像の一部を切り出しキャンバスの指定した位置に拡大・縮小して描画
「image cx , cy , cw , ch , x , y , w , h」
image: 貼り付けるイメージ
cx: 元画像の切り出し位置x
cy: 元画像の切り出し位置y
cw: 元画像の切り出し幅
ch: 元画像の切り出し高さ
x: 貼り付け先のキャンバス座標x
y: 貼り付け先のキャンバス座標y
w: 貼り付け画像の幅
h: 貼り付け画像の高さ
JSコード
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
con.drawImage( imgOb , 45 , 40 , 50 , 40 , 30 , 30 , imgOb.width , imgOb.height*1.5 );
});
キャンバスから画像データを読み取り描画
次はキャンバス内に既に描画されたデータを
ある特定の範囲だけ読み取り、
画像として別の部分に描画する方法です。
ただしこのコードはローカル上(サーバーにアップロードされていないパソコン内だけのファイル)のキャンバスで読み取ると「エラー」になってしまいます。
この作業をするときは、既にサーバーにアップロードしているファイルに対して行いましょう。
JSコード
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
con.drawImage( imgOb , 30 , 30 , imgOb.width*2 , imgOb.height*2);
const imgData = con.getImageData( 60 , 60 , 50 , 50);
con.putImageData( imgData , 200 , 30 );
});
「drawImage」で描画した画像を含めたキャンバス内の特定の範囲を読み取るために、
「getImageData」メソッドで座標(60,60)の場所から、横に50px、縦に50pxの範囲を読み取り、
その内容を「imgData」定数に代入、
読み取った画像データ「imgData」を、「putImageData」メソッドで、座標(200,30)の場所に貼り付けています。
キャンバスにマスクをかける
次は、パスで登録した図形の外側にマスクをかけて、
図形部分のみを表示させる方法です。
JSコード
con.moveTo(150,60);
con.lineTo(270,200);
con.lineTo(30,200);
con.closePath();
con.clip();
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
con.drawImage( imgOb , 30 , 30 , imgOb.width*3 , imgOb.height*3);
});
今回で言うと三角形のパスを登録して、
その後マスクをかけるための「clip」メソッドを記述します。
この「clip」メソッドを使用することで、このメソッドより前に登録したパス内のみ表示させます。
なお、「マスクをかける」と言うのは「見えなくする」と言うことなので、
ここでいうと「clip」メソッドを使用して、clipしたパス登録図形より外側を見えなくする、と言うことですね。
次はパス登録図形が「clip」メソッドの前に2つ以上あるパターンです。
JSコード
con.moveTo(150,60);
con.lineTo(270,200);
con.lineTo(30,200);
con.closePath();
con.moveTo(170,60);
con.lineTo(410,60);
con.lineTo(290,200);
con.closePath();
con.clip();
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
con.drawImage( imgOb , 30 , 30 , imgOb.width*3 , imgOb.height*3);
});
この様に問題なく2つのパス図形を適用させることができました!
マスクのイメージとしては
壁と窓があって、壁がマスクをかけた部分、景色(今回だと描画画像)が見える窓はclipしたパス登録図形部分
と言う感じですかね?
「fill」「stroke」の色の代わりに画像を適用
最初の方で「fillStyle」「strokeStyle」で「色」を選択していました。
その「色」にあたる部分に画像を適用する方法です。
例えば「fill」で画像で塗りつぶすと言うことですね。
JSコード
const imgOb = new Image();
imgOb.src = "画像URL";
imgOb.addEventListener("load", function() {
const imgPattern = con.createPattern( imgOb ,"repeat");
con.fillStyle = imgPattern;
con.fillRect(30,30,300,200);
});
画像ファイルの取得方法は先ほど説明した、
「Image」オブジェクトを「imgOb」としてインスタンス化、
インスタンス化した「Image」オブジェクトに画像のURLを与えました。
次に「createPattern」メソッドで画像の指定と表示方法を選択して定数に代入、
(表示方法は「repeat」だとキャンバスいっぱいに繰り返し、「repeat-x」だと横方向に繰り返し、「repeat-y」は縦方向に繰り返し、「no-repeat」は繰り返さず画像1枚だけ表示、になります)
「createPattern」を代入した「imgPattern」を、いつも色の指定をしていた「fillStyle」に指定し、
四角に塗りつぶす「fillRect」を記述、
「createPattern」から先は画像を読み込んでから適用させたいので、
「addEventListener」メソッドを使用し「load」後の処理とします。
そうすることで画像が表示されました!
これを「addEventListener」を無しで記述してみます。
JSコード
const imgOb = new Image();
imgOb.src = "画像URL";
const imgPattern = con.createPattern( imgOb ,"repeat");
con.fillStyle = imgPattern;
con.fillRect(30,30,300,200);
「fillRect」は適用されてますが、
画像を読み込む前に「fillStyle」で「imgPattern」を指定してしまっているので、表示されず真っ黒になってしまいましたね。
これでキャンバスの説明は以上になります。
ご購読ありがとうございました!