本題に入る前に一つ問題です。Canvasで文字を描画(fillTextメソッドを利用)する時、文字の縦基準位置を指定しない、つまりデフォルトの場合、文字は指定座標にたいしてどの位置に描画されるでしょう。次の3つの中からお選びください(点は指定座標の位置)
文字より上の位置 | 文字の縦中央の位置 | 文字より下の位置 |
というわけで全て不正解です。正解は下記のような位置関係となります。
文字の縦位置はtextBaselineというメソッドで指定できます。最初に問題にだした3つはそれぞれ”top”,”middle”,”bottom”を指定した時の位置となります。じゃあ、デフォルトは何かというと、”alphabetic”という値です。つまり、アルファベットの基準位置です(参考:textBaseline プロパティ – Canvasリファレンス – HTML5.JP)。いかにも英語圏の人が作った仕様ですね。最初、何でこんな変な位置で文字が描画されるんだと思っていました。
ここから本題。文字を描画する際に文字の後ろ側に白地も描画する方法です。ただし、textBaselineは”middle”ということを想定してします。白地をつける文字をcanvas上に書くために、下記のような関数を作りました。
function drawText(txt, x, y, opt){ var def = { baseline:'middle', align:'left', bkcolor:null, fontSize:20 }; $.extend(def,opt); ctx.font = def.fontSize + "px Meiryo"; ctx.textAlign = def.align; ctx.textBaseline = def.baseline; var leftX, topY, width, height; height = def.fontSize; width = ctx.measureText(txt).width; switch(def.baseline){ case 'top': height *= 1.5; topY = y; break; case 'middle': topY = y - (height/2); break; case 'bottom': height *= 1.5; topY = y - height; break; } switch(def.align){ case 'left': leftX = x; break; case 'center': leftX = x + (width/2); break; case 'right': leftX = x + width; break; } if(topY && leftX && def.bkcolor){ ctx.fillStyle = def.bkcolor; ctx.fillRect(leftX, topY, width, height); } ctx.fillStyle = ctx.strokeStyle; /*指定位置に点を打つ ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI*2, false); ctx.fill(); */ ctx.fillText(txt, x, y); }
引数にオプションを入れており、それによってデフォルトの動きとは異なる動きにできるようにしています。8行目で、jQueryのextendメソッドを使ってデフォルトオブジェクトに引数のオプション用のオブジェクトをマージしていますが、これぐらならわざわざextend使わなくても、下記のようにやれば同じ動作となるはずです。
for(var keyName in opt){ def[keyName] = opt[keyName]; }
10行目のtextAlignプロパティで指定の座標に対する文字描画の横位置を指定しています(参考:textAlign プロパティ – Canvasリファレンス – HTML5.JP)。15行目のmeasureTextメソッドで、描画する文字の幅を取得しています(参考:measureText() メソッド – Canvasリファレンス – HTML5.JP)。高さについては14行目で、フォントサイズと同じと考えています(そんな単純じゃなさそうなんですが、textBaselineがmiddleだとこれで大丈夫そうです)。
17~29行目で白地の上の座標を取得しています(一応、textBaselineがtopやbottomなことも考慮して、高さを1.5倍しています)。30~40行目で白地の左の座標を取得しています。後は、42~45行目で、白地の座標が取得できてかつ文字背景の色の指定がある場合(白だと”rgb(255,255,255)”や”#ffffff”)、白地用の長方形を描いています。そうして、52行目でテキストを描画しています。
というわけで、以下実行サンプル。
canvasを使ったお絵描き投稿システム
ラジオボタンから『テキスト』を選んだ後、canvas上をクリック(正確にはマウスダウンやタッチスタート)すると、テキストボックスが表示されるので、そこに文字を入力してエンターキーを押すと文字が描画されます。この時、一緒に表示されるラジオボタンで『不透明』を選択すると白地が描画されます。
本当は、白地の色や、フォントサイズの指定もできたらもっといいんでしょうが、それはそのうち・・・。
なお、呼び出し時のコードは下記のようになっています。
$('#mycanvas').on('touchstart mousedown', function(e){ e.preventDefault(); thisX = e.pageX || e.originalEvent.changedTouches[0].pageX; thisY = e.pageY || e.originalEvent.changedTouches[0].pageY; startX = thisX - $(this).offset().left - borderWidth; startY = thisY - $(this).offset().top - borderWidth; startX /= scaleFactor; startY /= scaleFactor; if(paintFlag === "pen"){ //(省略) }else if(paintFlag === "text"){ $('#inputTxtWindow').css({ // 文字描画用のウィンドウを表示 'top':thisY + 'px', 'left':thisX + 'px' }).show() .data('x', startX).data('y', startY) //指定の座標をウィンドウの情報にもたせる .find('#drawTxt').focus(); } }) // 文字描画 $('#drawTxt').on('keydown', function(e){ // エンターキー押下時 if(e.keyCode===13){ var text = $(this).val(); var $txtWindow = $('#inputTxtWindow'); var x = $txtWindow.data('x'); var y = $txtWindow.data('y'); var bkcolor; // 指定のラジオボタンが『不透明』であれば、文字背景に白色を指定 if( $('input[name="txtBkFlag"]:checked').val()==="unclear" ){ bkcolor = 'rgb(256,256,256)'; }else{ bkcolor = null; } // 文字描画 drawText(text, x, y, { bkcolor:bkcolor }); $txtWindow.hide(); } });
久々にcanvasページをいじりましたが、ものすごく読みづらいコードになってしまっていることに気づきました(今更ですが)。もうちょっとキレイなコードが書けるようになりたいものです。オブジェクト指向とか使って。
コメント