質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.12%

touchイベントで座標を獲得できない

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,567

kihokutarou

score 22

mauseイベントでは座標を獲得しトリミングができているスクリプトがあります。これにtouchイベントを追加させたくて悪戦苦闘しております。ブラウザはchrome、ローカルで完結させるプログラムです。
具体的な動作としてはupload_canvasという要素上で座標をとり、裏にあるlayer2というcanvasで四角形を(トリミング範囲)を取得し、それをbaseというcanvasから原寸大で書き出す、という動作です。

別の質問にてイベント処理をjQueryかaddEventListenerのどちらかに統一させたほうが良いとのアドバイスをいただきましたが、とりあえず現状のままで動かして、ゆくゆくはアドバイスを受け勉強をすすめたいと思っています。
//ここからタッチ試行
//ここまでタッチ試行
が今回追記した部分になります。
このとき、マウスイベントすら動かなくなってしまいますが、上記部分を削除すると普通に動作します。
どなたかおかしな点、改善点、ずばりここをこう!など教えていただけるととてもうれしいです。

字数の関係で以下はhtmlの要素タグ
<div id="draw_area">
<canvas id="upload_canvas" width="0" height="0"></canvas>
<canvas id="base" width="0" height="0" style="position: absolute; z-index: 0; display:none;"></canvas>
<canvas id="layer1" width="0" height="0" style="position: absolute; z-index: 0; display:none;"></canvas>
<canvas id="layer2" width="0" height="0" style="position: absolute; z-index: 1; display:none;"></canvas>
</div>
<!-- set this render canvas hidden -->
<canvas id="render" width="0" height="0"style="display:none;"></canvas>
<br>
<!--
<img src="#" id="i1"><br>
-->

--追記--
お二人のアドバイスをいただきながらエラーが出ないように修正をしました。エラーは出なくなりましたし、穴が開くほど記述を見返しているのですが、マウス操作ではトリミングできるのにタッチ操作ではトリミングというか矩形生成ができません。解決へのヒントをいただけるとありがたいです。
以下修正済み記述

var layer1 = document.getElementById("layer1");
var layer1Ctx = layer1.getContext("2d");

var layer2 = document.getElementById("layer2");
var layer2Ctx = layer2.getContext("2d");

var display = document.getElementById("upload_canvas");
var displayCtx = display.getContext("2d");

var base = document.getElementById("base");
var baseCtx = base.getContext("2d");

var render = document.getElementById("render");
var renderCtx = render.getContext("2d");

var layers = [layer1, layer2];

var is_down = false;
var trimming_begin_pos = { x: null, y: null };
var trimming_end_pos = { x: null, y: null };

var loaded_file = null;
var max_canvas_size = { width: 900, height: 600 };

$(function() {
  // load image file
  $("#upload_file").change(function() {

    var file = this.files[0];
    if (!file.type.match(/^image\/(png|jpeg|gif)$/)) return;

    var image = new Image();
    var reader = new FileReader();

    reader.onload = function(e) {
      image.onload = function() {

        var min_width = Math.min(this.width, max_canvas_size.width);
        var min_height = Math.min(this.height, max_canvas_size.height);
        var scale = Math.min(min_width / this.width, min_height / this.height);
        var size = {width: this.width * scale, height: this.height * scale};

        resizeCanvas(size.width, size.height);
        layer1Ctx.drawImage(image, 0, 0, size.width, size.height);
        updateCanvas();

        $("#upload_button").attr('filename', file.name);
        $("#upload_button").show();

        // load file on base buffer
        base.width = this.width/3;
        base.height = this.height/3;
        baseCtx.drawImage(image, 0, 0,this.width/3,this.height/3);
      }
      image.src = e.target.result;
    }

    reader.readAsDataURL(file);
  });

  // upload image
  $("#upload_button").click(function(){

    // get blob data from canvas
    var canvas = $('#render');
    var type = 'image/jpeg';
    var dataurl = canvas[0].toDataURL(type);
    var bin = atob(dataurl.split(',')[1]);
    var buffer = new Uint8Array(bin.length);
    for (var i = 0; i < bin.length; i++) {
      buffer[i] = bin.charCodeAt(i);
    }
    var blob = new Blob([buffer.buffer], {type: type});

    // upload
    var fd = new FormData();
    fd.append('filename', $(this).attr('filename'));
    fd.append('data', blob);
    $.ajax({
      type: 'POST',
      url: 'http://yoursite',
      data: fd,
      processData: false,
      contentType: false
    }).done(function(data) {
      // done
    });
  });

  // canvas controll
  function resizeCanvas(width, height) {
    for(var i = 0; i < layers.length; i++) {
      layers[i].width = width;
      layers[i].height = height;
    }
    display.width = width;
    display.height = height;
  }

  function updateCanvas() {
    displayCtx.drawImage(layer1, 0, 0, display.width, display.height); 
    displayCtx.drawImage(layer2, 0, 0, display.width, display.height);
  }


  display.addEventListener("mousemove", function(e){}, false);
  display.addEventListener("mouseout", function(e){}, false);
  $('#upload_canvas').on("mousemove", function(e) {
    if (is_down) {
      var mouse_pos = getMousePos(e);
      updateSelectArea(mouse_pos);
    }
  });
  $('#upload_canvas').on("mouseout", function(e) {
  });
  $('#upload_canvas').on("mousedown", function(e) {
    if (is_down == true) return;
    is_down = true;
    trimming_begin_pos = getMousePos(e);
  });
  $('#upload_canvas').on("mouseup", function(e) {
    is_down = false;
    trimming_end_pos = getMousePos(e);

    var begin_pos = trimming_begin_pos;
    var end_pos = trimming_end_pos;

    if (begin_pos.x == end_pos.x && begin_pos.y == end_pos.y) return;
    var begin = {x: 0, y:0};
    var end = {x:0, y:0};
    begin.x = Math.min(begin_pos.x, end_pos.x);
    begin.y = Math.min(begin_pos.y, end_pos.y);
    end.x = Math.max(begin_pos.x, end_pos.x);
    end.y = Math.max(begin_pos.y, end_pos.y);

    highlightTrimmingArea(begin, end);
    clip(begin, end);
  });

  function getMousePos(e) {
    var rect = display.getBoundingClientRect();
    return {
      x: e.clientX - rect.left, 
      y: e.clientY - rect.top};
  }

  function updateSelectArea(mouse_pos) {
    clear(layer2);
    drawRect(layer2,
             trimming_begin_pos.x, 
             trimming_begin_pos.y, 
             mouse_pos.x - trimming_begin_pos.x,
             mouse_pos.y - trimming_begin_pos.y,
             3, 'red');
    updateCanvas();
  }

  //ここからタッチ試行
  display.addEventListener("touchmove", function(e){}, false);
  display.addEventListener("touchend", function(e){}, false);
  $('#upload_canvas').on("touchmove", function(e) {
    if (is_down) {
      var touch_pos = getTouchPos(e);
      updateSelectArea(touch_pos);
    }
  });
  $('#upload_canvas').on("touchend", function(e) {
  });
  $('#upload_canvas').on("touchstart", function(e) {
    if (is_down == true) return;
    is_down = true;
    trimming_begin_pos = getTouchPos(e);
  });
  $('#upload_canvas').on("touchend", function(e) {
    is_down = false;
    trimming_end_pos = getTouchPos(e);

    var begin_pos = trimming_begin_pos;
    var end_pos = trimming_end_pos;

    if (begin_pos.x == end_pos.x && begin_pos.y == end_pos.y) return;
    var begin = {x: 0, y:0};
    var end = {x:0, y:0};
    begin.x = Math.min(begin_pos.x, end_pos.x);
    begin.y = Math.min(begin_pos.y, end_pos.y);
    end.x = Math.max(begin_pos.x, end_pos.x);
    end.y = Math.max(begin_pos.y, end_pos.y);

    highlightTrimmingArea(begin, end);
    clip(begin, end);
  });

  function getTouchPos(e) {
        var rect = display.getBoundingClientRect();
        return {
        x : e.clientX - rect.left,
        y : e.clientY - rect.top};
}



    function updateSelectArea(touch_pos) {
    clear(layer2);
    drawRect(layer2,
             trimming_begin_pos.x, 
             trimming_begin_pos.y, 
             touch_pos.x - trimming_begin_pos.x,
             touch_pos.y - trimming_begin_pos.y,
             3, 'red');
    updateCanvas();
  }


  //ここまでタッチ試行


  function highlightTrimmingArea(begin, end) {
    clear(layer2);
    var fill = "rgba(0, 0, 0, 0.5)";
    fillRect(layer2, 0, 0, layer2.width, begin.y, fill); // top
    fillRect(layer2, 0, begin.y, begin.x, end.y - begin.y, fill); // left
    fillRect(layer2, end.x, begin.y, layer2.width - begin.x, end.y - begin.y, fill); // right
    fillRect(layer2, 0, end.y, layer2.width, layer2.height - end.y, fill); // bottom
    updateCanvas();
  }

  function clear(canvas) {
    var ctx =  canvas.getContext("2d");
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  }

  function drawRect(canvas, x, y, width, height, line, style) {
    var ctx =  canvas.getContext("2d");
    ctx.beginPath();
    ctx.rect(x, y, width, height);
    ctx.lineWidth = line;
    ctx.strokeStyle = style;
    ctx.stroke();
  }

  function fillRect(canvas, x, y, width, height, style) {
    var ctx =  canvas.getContext("2d");
    ctx.beginPath();
    ctx.rect(x, y, width, height);
    ctx.fillStyle = style;
    ctx.fill();
  }

  function clip(begin, end) {
    var scale = base.width / display.width; // display scale
    var x = begin.x * scale;
    var y = begin.y * scale;
    var width = (end.x - begin.x) * scale;
    var height = (end.y - begin.y) * scale;

    render.width = width;
    render.height = height;
    renderCtx.drawImage( base, x, y, width, height, 0, 0, width, height);
  } 


})


どうかよろしくお願いします。

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • defghi1977

    2018/03/06 09:30

    あなたがまずすべきことはここで質問することではなく, コンソールに文法エラーが表示されていないかを確認することです.

    キャンセル

  • kihokutarou

    2018/03/06 09:33

    回答ありがとうございます。投稿前に確認したのですが、touch部分を入れると最終行あてにUncaught SyntaxError: Unexpected end of inputエラーが表示され、閉じカッコ等確認したのですが特に問題があるようには見えませんでした。

    キャンセル

回答 3

+2

SyntaxError: Unexpected end of inputエラーが表示され、閉じカッコ等確認したのですが特に問題があるようには見えませんでした。 

あなたのコードに問題があるから「SyntaxError」が出力されているのです. 

この手の問題を解決する最も効率の良い方法は問題のあるコードブロック全体を漫然と眺めるのではなく, 1メソッドずつコードに追加していきその都度文法エラーが存在しないかをコンソールで確認することです. いかにJavaScriptエンジンの性能が向上したとは言え, 常に問題のある箇所を的確に指摘できるわけではありませんから, 結局は人力で虱潰しに確認する必要があるのです.

NOTE:
この作業は苦痛この上ないので, 古くから「コピペはするな」とか「メソッドは短くしろ」とか「共通部分はまとめろ」とか言われているのです.


コード修正後の回答
NOTE:
筆者はタッチイベントを確認できる環境をもっていないので, 勝手ながらヒントの提示だけに留めさせていただきます.(ググればサンプルがたくさん出てくる筈)

TouchEventオブジェクトはマルチタッチをサポートするためにMouseEventオブジェクトと構成が全く異なります(clientX/clientYプロパティを持たない!)
そのため, 下記の部分でタッチ位置が取得できていないのです.

  $('#upload_canvas').on("touchend", function(e) {
    is_down = false;
    trimming_end_pos = getTouchPos(e);//←---TouchEventオブジェクトを直接渡している!
    //以下略
  });

  function getTouchPos(e) {
        var rect = display.getBoundingClientRect();
        return {
        x : e.clientX - rect.left,//←--e.clientXはundefined!
        y : e.clientY - rect.top};//←--e.clientYもundefined!
  }

ではどうすればよいのか?

TouchEventオブジェクトのtouches/changedTouchesプロパティから一つTouchオブジェクトを取得し, このTouchオブジェクトのclinetX/clientY値から座標を求めればよい(以降はMouseEvent版と同様)のです.

但し, TouchEventから取得されるTouchオブジェクトは一つとは限らないため, 常にTouchオブジェクトの識別子(identifier プロパティ)の値から現在追跡中のタッチ位置を検出するようにします.

※そのためには, touchstart(タッチの記憶), touchmove(タッチの追跡), touchend/touchcancel(タッチの開放)イベントの全てで適切にタッチ位置の選択を行う必要があります.

参考
https://developer.mozilla.org/ja/docs/Web/API/TouchEvent
https://developer.mozilla.org/ja/docs/Web/Guide/DOM/Events/Touch_events#Example

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/03/06 21:27

    であれば無理にtouchイベントを使う必要は無い気がしますね.Mozillaでも

    > 注記: 多くのケースで、 (タッチに特化していないコードでもユーザと対話できるようにするため) タッチイベントとマウスイベントの両方が送信されることに注意が必要です。タッチイベントを使用する場合は、マウスイベント送信されないようにするため event.preventDefault() を呼び出すべきです。

    としているので, マルチタッチ前提の高度な処理が必要ないのであれば素直にMouseEventで作り込むが筋っぽいです.(つーか混在させたからうまく行かなかったというオチ?)

    キャンセル

  • 2018/03/06 21:45

    touch試行前はやはりタッチ操作は全く効きませんでした。
    今回コメントいただいた後、
    function updateSelectArea(touch_pos) {
    clear(layer2);
    event.preventDefault() ;
    drawRect(layer2,
    trimming_begin_pos.x,
    trimming_begin_pos.y,
    touch_pos.x - trimming_begin_pos.x,
    touch_pos.y - trimming_begin_pos.y,
    3, 'red');
    updateCanvas();
    console.log(trimming_begin_pos.x);
    console.log(trimming_begin_pos.y);

    }
    と矩形処理時のtouchmoveの座標を追うと、「NaN」の連続なのでやはり受け渡しに難があるような気がします。
    これ以上長引かせるのも回答者様にも失礼かと思いますので、しばらく他から反応がなければクローズしようと思います。
    長々とありがとうございます。

    キャンセル

  • 2018/03/06 22:05

    一旦元のコードから離れ, シンプルな内容で動作検証を始められたら如何でしょう?

    > これ以上長引かせるのも回答者様にも失礼かと思いますので、
    むしろ解決してもいないのに質問をクローズするのは絶対におやめ下さい. 反応が無ければ無いなりにほっておけばよいのです.
    無理にクローズすることで良い回答の提示を妨げるだけでなく, あなた以外のtouchイベントで苦しんでおられる方がこの記事にたどり着いた際に落胆させてしまう等のマイナスの効能しか残しません.
    もちろん, 様々なヒントを元にご自身で問題が解決できた暁にはその内容をもってベストアンサーとされれば良いのです.

    キャンセル

check解決した方法

+1

https://teratail.com/questions/116557?whotofollow=
に本質問に続いての続報があります。

defghi1977様との会話の中で、touchイベントオブジェクトのご説明がありました。私は「いや現状で普通に座標が取れている」と回答しましたが、全体的にうまくいかず、また重ねて「一度コードから離れてみては」とアドバイスをもらったのでタッチイベントの勉強をしてみました。
display.addEventListener("touchstart",  function(e){}, false);
display.addEventListener("touchmove",  function(e){}, false);
display.addEventListener("touchend",  function(e){}, false);
display.addEventListener("touchcancel",  function(e){}, false);
と起こし(最後の行は入電した時のキャンセルイベントだと思うので本当は必要なし)、他のjQueryなどの文法も手直しをして、
function getTouchPos(e) {
var rect = display.getBoundingClientRect();
return {
x : event.touches[0].clientX - rect.left,
y : event.touches[0].clientY - rect.top};
}
でタッチ座標を獲得しようとしました。
本質問で求めた答えはここまでで半分ほど成立、座標を取ることはできしたが続けてエラーが出るはめに。それが文頭の次の質問となります。

結果的に全解決しましたし、逃げていたタッチイベントと一応向かい合えました。次は脱jQueryを目指します。特にdefghi1977様には感謝です。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

+1

閉じかっこを確認されたとのことですが

ぱっと見ですが、
・getTouchPos
・upload_canvasのaddEventListener
部分閉じられていないように見うけられますが。。。。

また、
updateSelectAreaも重複して存在しています。

文法チェックしてくれるエディタなり、ツールなりを活用することも検討された方が良いかと思います。
コピペミスだったらなおさら。。。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.12%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる