75
69

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

HTML canvas で画像を切り取る(crop) Javascript サンプルコード

Last updated at Posted at 2019-03-02

HTML canvas & Javascript を使った画像の切り抜き「Crop」のサンプルコードです。

ブラウザ上で画像を加工しようとしたら、canvas しかないわけですが、
いちばんやりたいのは「切り抜き:Crop」なんだけど、まぁ、シンプルなコードがほしかったのにみつから無いわけで。
無いときは自分で作るのがエンジニアの掟なわけです。

切り取る(crop) Demo

Mar-02-2019 15-57-23.gif
中央の枠の中を切り出すタイプ。先に切り出す枠を決めておいて、画像の方を確認しながら切り出す。
なんと言いますか、マウスで角から角へドラッグしたり、枠をつまんで移動するUIよりも、このほうが直感的でわかりやすいし、スマホとかタブレットにも向いているUIだと思うのです。

Crop してますか?

実際に触ってみてもらうのが早いでしょう。⬇︎

  • canvas 上で、マウスをドラッグすると切り抜く場所を変えられます。
  • canvas 上で、マウスのホイールをグリグリすると拡大率が変わります。
  • 「CROP」ボタンで、赤枠の部分が切り抜きされます。
  • 上部の「A」「B」「C」のボタンで画像が変わります。

See the Pen HTML crop canvas by yamazaki.3104 (@yamazaki3104) on CodePen.

実際のコードがこちら

Qiita を見ているひとたちに、細かい解説は不要だとは思うので、ざっと概要のみ書く

  • 主な canvas は編集用の id='cvs' と、ボタン「CROP」を押したあとに編集結果が入る id='out' の2つ
  • ボタン「A」「B」「C」は入力画像の変更を行う load_img() を呼ぶだけ。読み込まれた画像は img に入っていて、img は読み出すだけで加工はしない。
  • スライダーは id='scal' 画像の拡大縮小率を変えられるように用意したが、最終的にホイールイベントに対応したので、UIとしてはあまり役に立っていないかもしれない
  • 画像の切り抜き処理は drawImage() がやってます
  • イベントは、マウスとホイールとタッチを取っています(タッチイベントはスマホ&タブレット対応)
  • あー、一番のポイントは、外部ライブラリは未使用で、素のJavascriptのみで実装している点

特に難しいことは何もやっていない、平凡なコードだと思っています。マウスのドラッグで画像の位置を変えるコードがいちばん大変だった、、、とはいっても、HTMLも入れて90行程度なので、、、まぁ、半日くらいの作業量でしょうか。

<html>
<body>
<button onclick="load_img('https://lh3.ggpht.com/O0aW5qsyCkR2i7Bu-jUU1b5BWA_NygJ6ui4MgaAvL7gfqvVWqkOBscDaq4pn-vkwByUx=w300')">A</button>
<button onclick="load_img('https://www.mozilla.org/media/img/logos/firefox/logo-quantum-wordmark-white.bd1944395fb6.png')">B</button>
<button onclick="load_img('')">C</button><br>
<input id='scal' type='range' value='' min='10' max='400' oninput="scaling(value)" style='width: 300px;'><br>
<canvas id='cvs' width='300' height='400'></canvas><br>
<button onclick='crop_img()'>CROP</button><br>
<canvas id='out' width='200' height='200'></canvas>
<script>
    const cvs = document.getElementById( 'cvs' )
    const cw = cvs.width
    const ch = cvs.height
    const out = document.getElementById( 'out' )
    const oh = out.height
    const ow = out.width

    let ix = 0    // 中心座標
    let iy = 0
    let v = 1.0   // 拡大縮小率
    const img  = new Image()
    img.onload = function( _ev ){   // 画像が読み込まれた
        ix = img.width  / 2
        iy = img.height / 2
        let scl = parseInt( cw / img.width * 100 )
        document.getElementById( 'scal' ).value = scl
        scaling( scl )
    }
    function load_img( _url ){  // 画像の読み込み
        img.src = ( _url ? _url : 'https://1.bp.blogspot.com/-AoQB8vIvlcw/W8BOcXcEQ6I/AAAAAAABPZM/rXNbol90tXcxBZBlXsg__xix03b_F4nqwCLcBGAs/s800/pet_cat_omoi_sleep_man.png' )
    }
    load_img()
    function scaling( _v ) {        // スライダーが変った
        v = parseInt( _v ) * 0.01
        draw_canvas( ix, iy )       // 画像更新
    }

    function draw_canvas( _x, _y ){     // 画像更新
        const ctx = cvs.getContext( '2d' )
        ctx.fillStyle = 'rgb(200, 200, 200)'
        ctx.fillRect( 0, 0, cw, ch )    // 背景を塗る
        ctx.drawImage( img,
            0, 0, img.width, img.height,
            (cw/2)-_x*v, (ch/2)-_y*v, img.width*v, img.height*v,
        )
        ctx.strokeStyle = 'rgba(200, 0, 0, 0.8)'
        ctx.strokeRect( (cw-ow)/2, (ch-oh)/2, ow, oh ) // 赤い枠
    }
    function crop_img(){                // 画像切り取り
        const ctx = out.getContext( '2d' )
        ctx.fillStyle = 'rgb(200, 200, 200)'
        ctx.fillRect( 0, 0, ow, oh )    // 背景を塗る
        ctx.drawImage( img,
            0, 0, img.width, img.height,
            (ow/2)-ix*v, (oh/2)-iy*v, img.width*v, img.height*v,
        )
    }

    let mouse_down = false      // canvas ドラッグ中フラグ
    let sx = 0                  // canvas ドラッグ開始位置
    let sy = 0
    cvs.ontouchstart =
    cvs.onmousedown = function ( _ev ){     // canvas ドラッグ開始位置
        mouse_down = true
        sx = _ev.pageX
        sy = _ev.pageY
        return false // イベントを伝搬しない
    }
    cvs.ontouchend =
    cvs.onmouseout =
    cvs.onmouseup = function ( _ev ){       // canvas ドラッグ終了位置
        if ( mouse_down == false ) return
        mouse_down = false
        draw_canvas( ix += (sx-_ev.pageX)/v, iy += (sy-_ev.pageY)/v )
        return false // イベントを伝搬しない
    }
    cvs.ontouchmove =
    cvs.onmousemove = function ( _ev ){     // canvas ドラッグ中
        if ( mouse_down == false ) return
        draw_canvas( ix + (sx-_ev.pageX)/v, iy + (sy-_ev.pageY)/v )
        return false // イベントを伝搬しない
    }
    cvs.onmousewheel = function ( _ev ){    // canvas ホイールで拡大縮小
        let scl = parseInt( parseInt( document.getElementById( 'scal' ).value ) + _ev.wheelDelta * 0.05 )
        if ( scl < 10  ) scl = 10
        if ( scl > 400 ) scl = 400
        document.getElementById( 'scal' ).value = scl
        scaling( scl )
        return false // イベントを伝搬しない
    }
</script>
</body>
</html>

動作確認環境

いまのところ

  • Chrome バージョン: 72.0.3626.109(Official Build) (64 ビット)
  • Safari バージョン 12.0.2 (14606.3.4)
  • Edge 42.17134.1.0

でしか確認してません。ごめんなさい。
他の環境で確認できたら、追記します。

スマホ&タブレット向けにタッチイベントに対応したつもり

  • iOS 版 Google Chrome 72.0.3626.101
  • iOS 版 Safari 12.0

で動作確認すみ

2019/03/03: 追記
Firefox 65.0.2 で確認したらホイールのイベントが取れてなかった、、、なぜ??(原因不明の助)

75
69
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
75
69

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?