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

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

ただいまの
回答率

90.47%

  • JavaScript

    20935questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • canvas

    317questions

    HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。

canvasでのタッチイベント、clientXでエラーが出ます

解決済

回答 1

投稿

  • 評価
  • クリップ 1
  • VIEW 1,499

kihokutarou

score 16

https://teratail.com/questions/116264#
にて質問した同一内容になります。上記は本件が解決したら解決済みとしようと思っています。
回答者様より様々アドバイスを受けまして、mauseイベントで成功しているものにtouchイベントを追加しようと苦戦しています。
以下のscriptの「//ここからタッチ試行」よりが質問の中心で、上記質問時はタッチによる座標がNaN状態でした。
今回書き換えを行い、コンソールにtouchmove状態の座標もすべて獲得できていることを確認しました。しかしその後consoleでエラーが出ます。
Uncaught TypeError: cannot read property'clientX' of undefined 206行目
clientXというプロパティは読めませんよ、と大雑把にはそういう意味なんでしょうが、そもそもclientXはscreenX、screenY、pageX、pageYと同列のプロパティーなのでは?と混乱中です。

煮詰まっているものがもう少しで解決しそうなので、どなたかご指摘などいただけないでしょうか。

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);
    console.log(trimming_begin_pos.x);
    console.log(trimming_begin_pos.y);
  });

  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();
    console.log(trimming_begin_pos.x);
    console.log(trimming_begin_pos.y);
  }

  //ここからタッチ試行
  var 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);



  $('#upload_canvas').on("touchmove", function(e) {
    if (this.touch) {
      var touch_pos = getTouchPos(e);
      updateSelectArea2(touch_pos);
    }
  });
  $('#upload_canvas').on("touchend", function(e) {
  });
  $('#upload_canvas').on("touchstart", function(e) {
    if (this.touch == true) return;
    this.touch = true;
    trimming_begin_pos = getTouchPos(e);
  });
  $('#upload_canvas').on("touchend", function(e) {
    this.touch = 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 : event.touches[0].clientX - rect.left,
        y : event.touches[0].clientY - rect.top};
}


    function updateSelectArea2(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);
  }


  //ここまでタッチ試行




  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ページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+5

この問題を解決するには、getTouchPosを以下のように修正します。

function getTouchPos(e) {
  var rect = display.getBoundingClientRect();
  var touch = e.touches[0] || e.changedTouches[0];
  return {
    x : touch.clientX - rect.left,
    y : touch.clientY - rect.top
  };
}

Uncaught TypeError: cannot read property 'clientX' of undefined 206行目
clientXというプロパティは読めませんよ、と大雑把にはそういう意味なんでしょうが、そもそもclientXはscreenX、screenY、pageX、pageYと同列のプロパティーなのでは?と混乱中です。

undefined の 'clientX' は読めません」という意味ですので、問題は「clientX がない」ではなく「touches[0] がない(から touches[0].clientX もない)」です。screenX, screenY, pageX, pageY も同列のプロパティですから、それらを使ったとしても、同じエラーになったでしょう。

コードをみると touchstart, touchmove, touchend で getTouchPos を呼んでいますが、touchend ではうまくいきません。
event.touchesはそのイベントがおきたときに画面に触れている指の数だけ中身が入っています。

  • touchstart は指が画面に触れたときのイベントなので、touches[0] はあります。
  • touchmove は画面に触れた指が移動したときのイベントなので、touches[0] はあります。
  • touchend は指が画面から離れたときのイベントで、それが画面に触れていた最後の指だった場合、touches は空になります。そのため、getTouchPosでエラーになります。

そこで、touchesの代わりにchangedTouchesを使います。event.changedTouchesには、そのイベントを起こしたtouchが入っています。

https://codepen.io/hoo-chan/full/bvbRWp/
こちら↑に、touchイベントを見るためのアプリを作りました。指を離したときのtouchesとchangedTouchesの違いを確認してください。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2018/03/08 14:26

    ご回答ありがとうございます。
    var touch = e.touches[0] || e.changedTouches[0]; を
    var touch = event.touches[0] || event.changedTouches[0];
    と修正しましたら期待通りの動きができました。

    ご説明してくださったこと、もやもやが晴れてすっきりしました。質問でのエラー排出時のタイミングが、まさにtouchendのタイミング、言われてみればその判定は指が離れた直後であって離れた時(直前)の座標はとれていませんよね。本当に勉強になりました。
    ありがとうございます!

    キャンセル

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

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

同じタグがついた質問を見る

  • JavaScript

    20935questions

    JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

  • canvas

    317questions

    HTML5の<canvas>要素用のタグです。CanvasはHTML5から導入された、二次元の図形描写が可能な要素です。