サイトマップ

JavaScriptコードスニペット:画面上に落書きをする

HTMLの表現で苦手なことの1つに「斜めの線を引く」ということがあります。縦横の線であればborderを使ってそれらしいものを表現することができますが、斜めはそうもいきません。

1ドットのdiv要素を斜めに並べて表現するという荒業もありますが、ここはきちんと図形を描くという方法でやりたいものです。ただ、HTMLにおける図形の扱いはHTML5が出てくるまでは紆余曲折あって、各プラットフォーム共通の方法があるわけではありませんでした。そして、HTML5に対応したブラウザが出てきた現状であっても、世の中の多くの人が古いブラウザを使っているという現実があるので、より多くの人を救うためにはSVG、VML、Canvasの3パターンを実装する必要があります。

以下では、ご覧のWebページ上でフリーハンドで落書きをするプログラムを作ってみました。3パターンに対応しているので、かなり長ったらしくなっています。

[サンプルの実行]←クリックするとこの画面上でマウスを使った落書きができるようになります。何かキーを押すと落書きが消されて元に戻ります。bookmarklet化してあるので、ブックマーク(お気に入り)に登録しておくと、任意のWebページ上で実行可能です。
var grafitti = @INCLUDE(grafitti.js);
grafitti(document.body);
grafitti.js
/**
 * grafitti.js - 画面上に落書きをする
 */
function(panel){
    var canvas = {};
    //イベントを他の要素に取られないように透明のマスクを被せておきます。
    var mask = panel.appendChild(document.createElement('div'));
    mask.style.position = "absolute";
    mask.style.left = 0;
    mask.style.top = 0;
    mask.style.width = panel.offsetWidth + "px";
    mask.style.height = panel.offsetHeight + "px";
    mask.style.cursor = "hand";
    mask.style.backgroundColor = "#FFFFFF";
    mask.style.opacity = 0;
    mask.style.filter = 'alpha(opacity=0)';
 
    if (document.createElementNS) { //FireFoxのSVG
        var canvas = panel.appendChild(document.createElementNS('http://www.w3.org/2000/svg', 'svg'));
        canvas.style.position = "absolute";
        canvas.style.left = 0;
        canvas.style.top = 0;
        canvas.style.width = panel.offsetWidth + 'px';
        canvas.style.height = panel.offsetHeight + 'px';
        canvas.style.cursor = "hand";
        canvas.drawStart = function(evt) {
            evt = fixEvent(evt);
            window.status = 'start[' + evt.offsetX + ',' + evt.offsetY + ']';
            var s = canvas.shape = canvas.appendChild(document.createElementNS("http://www.w3.org/2000/svg", "polyline"));
            s.setAttribute('points', evt.offsetX + ',' + evt.offsetY + ' ');
            s.setAttribute('stroke', 'black');
            s.setAttribute('fill',   'none');
            s.setAttribute('stroke-width', '5');
            s.setAttribute('stroke-linecap', 'round');
            panel.onmousemove = canvas.draw;
            return false;
        }
        canvas.draw = function(evt) {
            evt = fixEvent(evt);
            window.status = 'draw[' + evt.offsetX + ',' + evt.offsetY + ']';
            canvas.shape.setAttribute('points', canvas.shape.getAttribute('points') + ' ' + evt.offsetX + ',' + evt.offsetY + ' ');
            return false;
        }
        canvas.drawEnd = function(evt) {
            if (!panel.onmousemove) return;
            evt = fixEvent(evt);
            window.status = 'end[' + evt.offsetX + ',' + evt.offsetY + ']';
            panel.onmousemove = null;
        }
    } else if (document.namespaces) { //IEのVML
        var left = 0, top = 0;
        var p = panel;
        while (p) {
          left += p.offsetLeft;
          top += p.offsetTop;
          p = p.offsetParent;
        }
        if (!document.namespaces.v) {
            document.namespaces.add("v", "urn:schemas-microsoft-com:vml");
            document.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);");
        }
 
        canvas = panel.appendChild(mask.cloneNode());
        canvas.style.backgroundColor = 'transparent';
        canvas.style.filter = '';
        canvas.drawStart = function(evt) {
            evt = fixEvent(evt);
            window.defaultStatus = 'start[' + evt.offsetX + ',' + evt.offsetY + ']';
            //v:shapeで線の先端の形状を指定する方法がわからないので、ちょっと角ばってしまいます。
            var s = canvas.shape = document.createElement('v:shape');
            s.style.position = 'absolute';
            s.style.left = left + 'px';
            s.style.top = top + 'px';
            s.style.width = canvas.offsetWidth + 'px';
            s.style.height = canvas.offsetHeight + 'px';
            s.setAttribute('strokeweight', 5);
            s.setAttribute('strokecolor', 'black');
            s.setAttribute('filled', 'false');
            s.setAttribute('path', 'm ' + evt.offsetX + ',' + evt.offsetY);
            s.setAttribute('coordsize', canvas.offsetWidth + ',' + canvas.offsetHeight);
            s.onmousemove = function(evt) {
                evt = fixEvent();
                return false;
            }
            canvas.appendChild(s);
            canvas.onmousemove = canvas.draw;
            return false;
        }
        canvas.draw = function(evt) {
            evt = fixEvent(evt);
            window.defaultStatus = 'draw[' + evt.offsetX + ',' + evt.offsetY + ']';
            var s = canvas.shape;
            s.path.value = s.path.value.replace(/ e$/, '') + ' l ' + evt.offsetX + ',' + evt.offsetY + ' e';
            return false;
        }
        canvas.drawEnd = function(evt) {
            if (!canvas.onmousemove) return;
            evt = fixEvent(evt);
            window.status = 'end[' + evt.offsetX + ',' + evt.offsetY + ']';
            canvas.onmousemove = null;
            return false;
        }
    } else { //HTMLのCanvas
        var canvas = document.createElement('canvas');
        canvas.setAttribute('width', panel.clientWidth + 'px');
        canvas.setAttribute('height', panel.clientHeight + 'px');
        canvas.style.position = "absolute";
        canvas.style.left = 0;
        canvas.style.top = 0;
        canvas.style.cursor = "hand";
        panel.appendChild(canvas);
        canvas.drawStart = function(evt) {
            evt = fixEvent(evt);
            window.status = 'start[' + evt.offsetX + ',' + evt.offsetY + ']';
            var ctx = canvas.getContext('2d');
            ctx.lineWidth = 5;
            ctx.lineCap = 'round';
            ctx.beginPath();
            ctx.moveTo(evt.offsetX, evt.offsetY);
            panel.onmousemove = canvas.draw;
        }
        canvas.draw = function(evt) {
            evt = fixEvent(evt);
            window.status = 'draw[' + evt.offsetX + ',' + evt.offsetY + ']';
            var ctx = canvas.getContext('2d');
            ctx.lineTo(evt.offsetX, evt.offsetY);
            ctx.stroke();
        }
        canvas.drawEnd = function(evt) {
            if (!canvas.onmousemove) return;
            evt = fixEvent(evt);
            window.status = 'end[' + evt.offsetX + ',' + evt.offsetY + ']';
            panel.onmousemove = null;
        }
    }
    panel.onmousedown = canvas.drawStart;
    document.onmouseup = canvas.drawEnd;
    document.onkeypress = function(evt) {
        panel.onmousedown = null;
        document.onmouseup = null;
        document.onkeypress = null;
        document.onmousemove = null;
        panel.removeChild(canvas);
        panel.removeChild(mask);
    }
 
    //以下のコードは http://shorindo.com/research:1308704774 からの切り貼りで、本質ではありません。
    //ただし、offsetXXの値を持たないsvg要素に対応するために改変しています。
    var fixEvent = function(evt) {
        if (!window.event) {
            if (typeof evt.offsetX != 'number') { //not safari
                evt.offsetX = evt.pageX;
                evt.offsetY = evt.pageY;
                var el = evt.target;
                while (el) {
                    evt.offsetX -= el.offsetLeft ? el.offsetLeft : 0;
                    evt.offsetY -= el.offsetTop ? el.offsetTop : 0;
                    el = el.offsetParent ? el.offsetParent : el.parentNode;
                }
            }
        } else {
            evt = window.event;
            evt.target = evt.srcElement;
            evt.preventDefault = function() { this.returnValue = false; };
            evt.stopPropagation = function() { this.cancelBubble = true; };
            switch(evt.type) {
            case 'click':
            case 'mousedown':
            case 'mouseup':
            case 'mousemove':
                evt.which = evt.button==4 ? 3 : evt.button;
		if (document.compatMode == 'CSS1Compat') {
		    evt.pageX = evt.clientX + document.documentElement.scrollLeft;
		    evt.pageY = evt.clientY + document.documentElement.scrollTop;
		} else {
		    evt.pageX = evt.clientX + document.body.scrollLeft;
		    evt.pageY = evt.clientY + document.body.scrollTop;
		}
                break;
            }
        }
 
        evt.preventDefault();
        evt.stopPropagation();
        evt.returnValue = false;
        return evt;
    }
}

関連コンテンツ

参照

改訂履歴

  • 2011/07/06 初版リリース
  • 2011/07/29 bookmarklet化
 
research/1309647163.txt · 最終更新: 2011/07/29 09:24 by Kazuyuki Matsuda
特に明示されていない限り、本サイトの内容は次のライセンスに従います:Copyright(C) 2011 Shorindo, Inc. All Rights Reserved
Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki