18
4

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.

jQueryAdvent Calendar 2017

Day 6

画像に.draggable()と.resizable()とCSSのtransform:scale()を同時に使うとずれていくのをどうにかする

Last updated at Posted at 2017-12-06

※Qiita初投稿です分かりづらいところがあったらご指摘いただければと思います。

#やりたいこと
jQuery(jQueryUI)を使ってブラウザ上のキャンバス(canvasは使ってません。ただの白いdivです。)で画像を移動したり拡大縮小したりできるようにします。
パワポのイメージです。
最終的にはそれをPDF化するのですが、ここでは扱いません。

#環境
jquery-ui-1.8.17

#最初の問題

index.css
#imgDiv {
    position: absolute;
}
index.html
<div id="canvas">
    <img id="img" src="/images/img.jpg">
</div>
index.js
$(function() {
    $('#img').draggable().resizable();
})

とりあえずこのようなかんじで画像に.draggable()と.resizable()をつけてみるも、うまくいきません。
具体的に言うとリサイズはできますがドラッグができません。
そこで、imgをdivで囲ってdivをリサイズ・ドラッグできるようにします。

index.html
<div id="canvas">
    <div id="imgDiv">
        <img id="img" src="/images/img.jpg">
    </div>
</div>
index.js
$(function() {
    $('#imgDiv').draggable().resizable();
})

#無事、ドラッグとリサイズができるように・・・
と思いきや。
リサイズ時にdivのみリサイズされ、中の画像がついてこない。。。
optionを調べたら"alsoResize"なるものがあったので使ってみます。(ついでに他のoptionもいくつかつけます。)

index.js
$(function() {
    $('#imgDiv').draggable({
        create: function() {
            // 生成されたときにdivのサイズとimgのサイズを合わせる
            var $self = $(this);
            var $img  = $('#img');
            $self.css({
                width : $img.css('width'),
                height: $img.css('height')
            });
        }
    }).resizable({
        handles    : 'se, ne, sw, nw', // リサイズできる方向を制御(四つ角)
        aspectRatio: true,            // アスペクト比を固定 
        alsoResize: $('#img')          // リサイズを同期させる要素
    });
})

ちゃんとdivとimgがぴったりくっついたまま、リサイズ・ドラッグができました!
やったね!

と喜ぶのもつかの間。

#小さい画面だと操作しにくいな、拡大表示できるようにしよう
なんてことを思ってしまい、CSS3のtransform: scale();を使ってみることにしました。
拡大ボタンを押したら拡大、縮小ボタンを押したら縮小、というかんじで実装します。

index.html
<button type="button" id="zoomMinus" class="zoom_btn">縮小</button>
<button type="button" id="zoomPlus" class="zoom_btn">拡大</button>
<div id="canvas">
    <div id="imgDiv">
        <img id="img" src="/images/img.jpg">
    </div>
</div>
index.js
$(function() {
    var zoomScale = 1;
    $('.zoom_btn').on('click', function() {
        // 拡大のときは現在の倍率に+0.1、縮小のときは-0.1
        zoomScale += $(this).attr('id') === 'zoomMinus' ? -0.1 : 0.1;
        $('#canvas').css({
            'transform'       : 'scale(' + zoomScale + ')',
            'transform-origin': '0 0'
        })
    });
})

キャンバスの拡大縮小はいいかんじです!
しかし、キャンバスの倍率を変更すると、リサイズもドラッグもなんかうまくいかない、、、
・ドラッグするときにマウスとずれる
・リサイズするときにもマウスとずれる
・右下(se)のリサイズハンドル以外でリサイズすると、持っているリサイズハンドル以外の角も固定されずずれていく

どうやら公式のバグ?っぽいです。

#ドラッグするときにマウスとずれないようにする

index.js
$(function() {
    // resizableは省略
    $('#imgDiv').draggable({
        create: function() {
            // 生成されたときにdivのサイズとimgのサイズを合わせる
            var $self = $(this);
            var $img  = $('#img');
            $self.css({
                width : $img.css('width'),
                height: $img.css('height')
            });
        },
        drag: function(e, ui) {
            // 拡大倍率で割ることで位置を調整
            ui.position.left = ui.position.left / zoomScale;
            ui.position.top  = ui.position.top / zoomScale;
        }
    });
})

無事ドラッグ時にマウスとずれないようになりました。

#リサイズするときにマウスとずれないようにする
こちらはちょっと手強いです。
持っているリサイズハンドルによってずれ方が違うので「今どこのリサイズハンドルでリサイズしているか」ということを判定して処理を分岐させます。

index.js
$(function() {
    // draggableは省略
    $('#imgDiv').resizable({
        handles    : 'se, ne, sw, nw',
        aspectRatio: true,
        alsoResize: $('#img'),
        create: function () {
            // 現在の位置情報をoptionに追加
            var $resizeDiv = $(this);
            $resizeDiv.resizable('option', {
                'orgLeft'  : parseInt($resizeDiv.css("left"), 10),
                'orgTop'   : parseInt($resizeDiv.css("top"), 10),
                'orgRight' : parseInt($resizeDiv.css("right"), 10),
                'orgBottom': parseInt($resizeDiv.css("bottom"), 10)
            });
        },
        start: function (e) {
            var $resizableDiv = $(this);
            var $canvas = $('#canvas');
            var divLeft = $resizableDiv.resizable('option').orgLeft * zoomScale + $canvas.offset().left;
            var divTop  = $resizableDiv.resizable('option').orgTop * zoomScale + $canvas.offset().top;
            var difX    = Math.abs(divLeft - e.clientX);
            var difY    = Math.abs(divTop  - e.clientY);
            // リサイズしているリサイズハンドルを判定
            switch (true) {
                case (difX < 10 && difY < 10):
                    resizeHandle = 'nw';
                    break;
                case (difX < 10 && difY > 10):
                    resizeHandle = 'sw';
                    break;
                case (difX > 10 && difY < 10):
                    resizeHandle = 'ne';
                    break;
                case (difX > 10 && difY > 10):
                    resizeHandle = 'se';
            }
        },
        resize: function (e, ui) {
            var $resizableDiv = $(this);
            var $options   = $resizableDiv.resizable('option');
            var orgLeft    = $options.orgLeft;
            var orgTop     = $options.orgTop;
            var orgRight   = $options.orgRight;
            var orgBottom  = $options.orgBottom;
            // 持っているリサイズハンドルによって固定する位置を指定
            // optionに入れておいた初期位置で固定
            switch (resizeHandle) {
                case 'nw':
                    $resizableDiv.css({
                        left  : '',
                        top   : '',
                        right : orgRight + 'px',
                        bottom: orgBottom + 'px'
                    });
                    break;
                case 'sw':
                    $resizableDiv.css({
                        left  : '',
                        top   : orgTop + 'px',
                        right : orgRight + 'px',
                        bottom: ''
                    });
                    break;
                case 'ne':
                    $resizableDiv.css({
                        left  : orgLeft + 'px',
                        top   : '',
                        right : '',
                        bottom: orgBottom + 'px'
                    });
                    break;
                case 'se':
                    $resizableDiv.css({
                        left  : orgLeft + 'px',
                        top   : orgTop + 'px',
                        right : '',
                        bottom: ''
                    });
                    break;
            }

            // zoomScaleによって画像サイズがマウスと合わなくなるので調整
            var changeWidth = ui.size.width - ui.originalSize.width;
            var newWidth = ui.originalSize.width + changeWidth / zoomScale;
            var changeHeight = ui.size.height - ui.originalSize.height;
            var newHeight = ui.originalSize.height + changeHeight / zoomScale;
            ui.size.width = newWidth;
            ui.size.height = newHeight;
        },
        stop : function () {
            // 現在の位置情報をoptionに追加
            var $resizeDiv = $(this);
            $resizeDiv.resizable('option', {
                'orgLeft'  : parseInt($resizeDiv.css("left"), 10),
                'orgTop'   : parseInt($resizeDiv.css("top"), 10),
                'orgRight' : parseInt($resizeDiv.css("right"), 10),
                'orgBottom': parseInt($resizeDiv.css("bottom"), 10)
            });
        }
    });
})

いきなり長くなってしまいました、、、
やっていることとしては、
①初期状態でのtop, bottom, left, right をoptionに保存する
②リサイズするときに持っているリサイズハンドルが四方のどこか判定
③持っているリサイズハンドルによってtop, bottom, left, right のどれを固定するか判定

例えば、右下のリサイズハンドルでリサイズするときはtopとleftを固定、左上ならrightとbottomを固定、といった具合です。
これでリサイズ時もずれなくなりました。

あとは、ドラッグ完了時にも初期位置を保存して完了です。
長かったー
もっと簡潔に書けるような気もするので、ぜひご教示ください。

ご覧いただきありがとうございました。

18
4
2

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
18
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?