キャンバス上の100行:パート1

はじめに、1人のhabrayuzerの誕生日おめでとうございます。 大きく成長し、スマートになり、最終的にGraphics2Dキャンバスフレームワークを許容範囲内に仕上げます。

誕生日おめでとう。 :P



今年の夏、面白い考えが浮かびました。キャンバスのマイクロライブラリを100行で書いているとしたら、そこに何が収まりますか?..最も詳細な答えは、夕方に書くことができます。 そして、この記事のアイデアが生まれました。



私は、OOP、イベント、およびアニメーションをキャンバスに実装することを提案します-最も頻繁に必要な(IMHO)もの...そして、これらすべてを100行で行います。 パート1



免責事項:ここでは、(場合によっては)完全に不必要な歪みを待って、いくつかのコード文字を保存しています。 著者(これは私です)は、これがマイクロライブラリでも可能であり、非常に頻繁に行われると信じています。 もちろん、これがパフォーマンスに違反しない場合。



カットの下であなたに会えてうれしい;)



アイデアから始めましょう(最初に、OOP)。 3つの主なオブジェクト:パス、画像、テキスト。 たとえば、ミニライブラリに長方形や円を実装する必要はありません。これらはパスを介して簡単に作成できます。 スプライトのように、写真を通して。 等

オブジェクトの最初の引数はそのコンテンツです。

2つ目は、描画前にキャンバスに設定されるスタイルです。



私はそれをラットと呼びます:P

Rat = function(context){ this.context = context; };
      
      







方法



このような何かがいいだろう:



 var path = rat.path([ ['moveTo', 10, 10], ['lineTo', 100, 100], ['lineTo', 10, 100], ['closePath'] ], { fillStyle: 'red', strokeStyle: 'green', lineWidth: 4 });
      
      







3つのオブジェクトすべてについて、コンテキストプロパティ、スタイルのオブジェクトなどを設定する必要があります。

 Rat.init = function(cls, arg){ cls.opt = arg[0]; cls.style = arg[1] || {}; cls.context = arg[2]; cls.draw(arg[2].context); };
      
      





すべてがはっきりしているように見える? 各オブジェクトには、opt(1引数)、style(2nd)、context(context)、およびこのオブジェクトを描画するdraw(ctx)関数の3つのプロパティがあります。



私たちのクラス:

 Rat.Path = function(opt, style, context){ Rat.init(this, arguments); };
      
      





はい、奇妙なことに、デザイナーがすべてです。



最も重要な:レンダリング:

 Rat.Path.prototype = { draw: function(ctx){ this.process(function(ctx){ if(this.style.fillStyle) ctx.fill(); if(this.style.strokeStyle) ctx.stroke(); }, ctx); }, process: function(callback, ctx){ ctx = ctx || this.context.context; Rat.style(ctx, this.style); ctx.beginPath(); this.opt.forEach(function(func){ ctx[func[0]].apply(ctx, func.slice(1)); }); var result = callback.call(this, ctx); ctx.restore(); return result; } };
      
      





ここでのプロセス関数は偶然ではありません。あちこちで必要になります。

  isPointIn: function(x,y, ctx){ return this.process(function(ctx){ return ctx.isPointInPath(x, y); }, ctx); }
      
      





なぜコールバックするのですか? うーん...美のために。



3つのオブジェクトすべてに共通するRat.style関数は、単にプロパティをキャンバスに転送します。 変換も必要であることを忘れないでください:

 //     ,       //  Rat.notStyle = "translate0rotate0transform0scale".split(0); Rat.style = function(ctx, style){ ctx.save(); style.origin && ctx.translate.apply(ctx, style.origin); style.rotate && ctx.rotate(style.rotate); style.scale && ctx.scale.apply(ctx, style.scale); style.origin && ctx.translate(-style.origin[0], -style.origin[1]); style.translate && ctx.translate.apply(ctx, style.translate); // ,      origin? style.transform && ctx.transform.apply(ctx, style.transform); Object.keys(style).forEach(function(key){ if(!~Rat.notStyle.indexOf(key)) ctx[key] = style[key]; }); };
      
      







あぁ、負けないで、すべてを説明します。 !~Rat.notStyle.indexOf(key)



Rat.notStyle.indexOf(key) != -1



と同じです。 ただし、これはマイクロライブラリです。



最後に、クラスのインスタンスを作成して返すコンテキスト関数:

 Rat.prototype = { path : function(opt, style){ return new Rat.Path(opt, style, this); }, };
      
      







すべて、パスを描くことができます。 やった!



そして、基本的なスタイルに加えて、Rat.styleに見られるように、変換があります:



 var path = rat.path([ ['moveTo', 10, 10], ['lineTo', 100, 100], ['lineTo', 10, 100], ['closePath'] ], { fillStyle: 'red', strokeStyle: 'green', lineWidth: 4, rotate: 45 / 180 * Math.PI, origin: [55, 55] });
      
      



画像がトリミングされているのは ゼロ座標で描画されます。



写真



2つの引数の原則に従って、このようなクラスが必要です。

 var img = new Image(); img.src = "image.jpg"; img.onload = function(){ rat.image(img); }
      
      





さらに、スタイルでは、パラメータwidthheightおよびcrop (4つの数字の配列)を渡すことができます。 すべてが元のdrawImage CanvasRendering2DContextと同じです。



再びクラスコンストラクター:

 Rat.Image = function(opt, style, context){ Rat.init(this, arguments); };
      
      







レンダリングは次のようになります。

 Rat.Image.prototype.draw = function(ctx){ Rat.style(ctx, this.style); if(this.style.crop) ctx.drawImage.apply(ctx, [this.opt, 0, 0].concat(this.style.crop)); else ctx.drawImage(this.opt, 0, 0, this.style.width || this.opt.width, this.style.height || this.opt.height); ctx.restore(); };
      
      





すべてがシンプルなようです。



そして最後に、もちろん:

 Rat.prototype = { ... image : function(opt, style){ return new Rat.Image(opt, style, this); }, };
      
      







ほら、写真があります。



テキスト



3番目のグローバルオブジェクト:

 var text = rat.text("Hello, world!", { fillStyle: 'blue' });
      
      



maxWidthプロパティもあります。



コンストラクター:

 Rat.Text = function(){ Rat.init(this, arguments); };
      
      







レンダリングは非常に簡単です。 そして、いつものように、解決策はあまりきれいではありませんが、うまくいきます)。

 Rat.Text.prototype.draw = function(ctx){ Rat.style(ctx, this.style); if(this.style.fillStyle) ctx.fillText(this.opt, 0, 0, this.style.maxWidth || 999999999999999); if(this.style.strokeStyle) ctx.strokeText(this.opt, 0, 0, this.style.maxWidth || 9999999999999999); ctx.restore(); };
      
      







キャンバス上のテキストを測定することもできます。 幅、はい。 高さはフォントサイズによって決まります。

 Rat.Text.prototype.measure = function(){ var ctx = this.context.context; Rat.style(ctx, this.style); var w = ctx.measureText(this.opt).width; ctx.restore(); return w; };
      
      







忘れないでください:

 Rat.prototype = { ... image : function(opt, style){ return new Rat.Image(opt, style, this); }, };
      
      







ささいなこと



時には、許し、忘れ、すべてを捨て、ゼロから始める必要があります。 そのような場合には、明確な機能があります:

 Rat.prototype = { ... clear: function(){ var cnv = this.context.canvas; this.context.clearRect(0, 0, cnv.width, cnv.height); } };
      
      





それ以外には、 drawがあり、配列からすべてのオブジェクトを描画します。

 Rat.prototype = { ... draw: function(elements){ var ctx = this.context; elements.forEach(function(element){ element.draw(ctx); }); } };
      
      







例:



さて、今...例えば、キャンバスにボタンを配置しましょう(最も簡単なもの):

 //  var path = rat.path([ ['moveTo', 10, 10], ['lineTo', 100, 10], ['lineTo', 100, 40], ['lineTo', 10, 40], ['closePath'] ], { fillStyle: '#eee', strokeStyle: '#aaa', lineWidth: 2 }); //  var text = rat.text("Hello, world", { translate: [55, 28], textAlign: 'center', fillStyle: 'black' });
      
      









そして放してください...マウスをホバーすると、ハイライトされます:

 var bounds = ctx.canvas.getBoundingClientRect(); var hover = false; ctx.canvas.addEventListener('mousemove', function(e){ var x = e.clientX - bounds.left, y = e.clientY - bounds.top; if(x > 10 && x < 100 && y > 10 && y < 40){ if(hover) return; hover = true; path.style.fillStyle = '#ccc'; rat.clear(); rat.draw([path, text]); } else if(hover){ hover = false; path.style.fillStyle = '#eee'; rat.clear(); rat.draw([path, text]); } });
      
      









なんで?



最も興味深いのは、ベースキャンバスで、ほぼ同じ量のコードをほぼ同じように入力できることです。

非表示のテキスト
 //  var path = { fill: '#eee', draw: function(){ ctx.moveTo(10, 10); ctx.lineTo(100, 10); ctx.lineTo(100, 40); ctx.lineTo(10, 40); ctx.closePath(); ctx.fillStyle = this.fill; ctx.strokeStyle = '#aaa'; ctx.lineWidth = 2; ctx.fill(); ctx.stroke(); } }; //  var text = { draw: function(){ ctx.textAlign = 'center'; ctx.fillStyle = 'black'; ctx.fillText("Hello, world", 55, 28); } }; path.draw(); text.draw(); var bounds = ctx.canvas.getBoundingClientRect(); var hover = false; ctx.canvas.addEventListener('mousemove', function(e){ var x = e.clientX - bounds.left, y = e.clientY - bounds.top; if(x > 10 && x < 100 && y > 10 && y < 40){ if(hover) return; hover = true; path.fill = '#ccc'; ctx.clearRect(0, 0, 800, 400); path.draw(); text.draw(); } else if(hover){ hover = false; path.fill = '#eee'; ctx.clearRect(0, 0, 800, 400); path.draw(); text.draw(); } });
      
      





しかし、これは100行が書かれて初めて明らかになりました...

github.com/keyten/Rat.js/blob/master/rat.js



さて...次のパート(このトピックがHabrahabrにとって興味深い場合)では、マウス処理の実装とパート3-アニメーションを示します。 すべてが100行に戻っています(うまくいくかどうかを確認しましょう)。

私は誕生日を祝います。



すべての興味深いコード!




All Articles