HTML5使用canvas实现摇杆

607 阅读1分钟
Joystick.js
function Joystick(id) {
  let canvas = document.getElementById(id); //画板

  let nei = new Image(); //内摇杆图片
  let wai = new Image(); //外摇杆图片

  let waiSize = canvas.height; //外摇杆大小
  let neiSize = waiSize * 0.6; //内摇杆大小

  let centerX = waiSize / 2; //摇杆中心x坐标
  let centerY = waiSize / 2; //摇杆中心y坐标

  let touchX = canvas.offsetLeft,
    touchY = canvas.offsetTop;

  let jc = canvas.getContext("2d"); //画布

  //摇杆头应当移动到的位置
  let jx = 0,
    jy = 0;

  // 定时器
  let timeId;

  //图片加载完成时执行这俩函数
  nei.onload = () =>
    jc.drawImage(
      nei,
      centerX - neiSize / 2,
      centerY - neiSize / 2,
      neiSize,
      neiSize
    );
  wai.onload = () =>
    jc.drawImage(
      wai,
      centerX - waiSize / 2,
      centerY - waiSize / 2,
      waiSize,
      waiSize
    );

  //绘图函数(绘制图形的时候就是用户观察到摇杆动了,所以取名是move)
  function move() {
    jc.clearRect(
      centerX - waiSize / 2,
      centerY - waiSize / 2,
      waiSize,
      waiSize
    ); //清空画板
    jc.drawImage(
      wai,
      centerX - waiSize / 2,
      centerY - waiSize / 2,
      waiSize,
      waiSize
    ); //画底座
    jc.drawImage(
      nei,
      centerX - neiSize / 2 + jx,
      centerY - neiSize / 2 + jy,
      neiSize,
      neiSize
    ); //画摇杆头
    requestAnimationFrame(move); //下一次绘图
  }

  nei.src = require("../assets/imgs/probe/nei.png"); //加载图片
  wai.src = require("../assets/imgs/probe/wai.png"); //加载图片

  //加载的时候先把摇杆绘制出来再说
  move();
  let effectiveFinger = -1; //当前有效手指

  // 回调
  function callback() {
    loop();
    sCallback(jx, jy);
  }

  function loop() {
    if (timeId) {
      requestAnimationFrame(callback);
    }
  }

  function ontouchstart(event) {
    //判断是否击中摇杆头
    for (let i = 0; i < event.touches.length; i = i + 1) {
      if (
        Math.sqrt(
          Math.pow(event.touches[i].clientX - centerX - touchX, 2) +
            Math.pow(event.touches[i].clientY - centerY - touchY, 2)
        ) <=
        waiSize / 2 - neiSize / 2
      ) {
        effectiveFinger = i;
        console.log("finger No." + i + " is effectiveFinger now.");

        //开启每帧处理
        if (timeId) timeId = null;
        timeId = true;
        loop();
      }
    }
  }

  function ontouchend(event) {
    if (timeId) timeId = null;
    //若有效手指离开,那就把内摇杆放中间
    if (event.touches[effectiveFinger] == null) {
      if (event.touches[0] == null) {
        jx = 0;
        jy = 0;
      }
      effectiveFinger -= 1;
    }
  }

  function ontouchmove(event) {
    if (effectiveFinger !== -1)
      if (
        Math.sqrt(
          Math.pow(
            event.touches[effectiveFinger].clientX - centerX - touchX,
            2
          ) +
            Math.pow(
              event.touches[effectiveFinger].clientY - centerY - touchY,
              2
            )
        ) <=
        waiSize / 2 - neiSize / 2
      ) {
        //是否触摸点在摇杆上
        jx = event.touches[effectiveFinger].clientX - centerX - touchX;
        jy = event.touches[effectiveFinger].clientY - centerY - touchY;
      } else {
        //否则计算摇杆最接近的位置
        let x = event.touches[effectiveFinger].clientX - touchX,
          y = event.touches[effectiveFinger].clientY - touchY,
          r = waiSize / 2 - neiSize / 2;

        let ans = GetPoint(centerX, centerY, r, centerX, centerY, x, y);
        //圆与直线有两个交点,计算出离手指最近的交点
        if (
          Math.sqrt((ans[0] - x) * (ans[0] - x) + (ans[1] - y) * (ans[1] - y)) <
          Math.sqrt((ans[2] - x) * (ans[2] - x) + (ans[3] - y) * (ans[3] - y))
        ) {
          jx = ans[0] - centerX;
          jy = ans[1] - centerY;
        } else {
          jx = ans[2] - centerX;
          jy = ans[3] - centerY;
        }
      }
    // event.preventDefault(); //防止页面滑动,取消掉默认的事件
  }
  canvas.addEventListener("touchstart", ontouchstart);
  canvas.addEventListener("touchmove", ontouchmove);
  canvas.addEventListener("touchend", ontouchend);
  requestAnimationFrame(move); //开始绘图

  //计算圆于直线的交点(这一块好难啊)
  //圆的坐标为cx,cy 半径为r
  //直线上两点的坐标分别为(stx,sty)、(edx,edy)
  function GetPoint(cx, cy, r, stx, sty, edx, edy) {
    //(x-cx)^2+(y-cy)^2=r^2
    //y=k*x+b
    let k = (edy - sty) / (edx - stx);
    let b = edy - k * edx;
    //(1 + k^2)*x^2 - x*(2*cx -2*k*(b -cy) ) + cx*cx + ( b - cy)*(b - cy) - r*r = 0
    let x1, y1, x2, y2;
    let c = cx * cx + (b - cy) * (b - cy) - r * r;
    let a = 1 + k * k;
    let b1 = 2 * cx - 2 * k * (b - cy);

    let tmp = Math.sqrt(b1 * b1 - 4 * a * c);

    x1 = (b1 + tmp) / (2 * a);
    y1 = k * x1 + b;

    x2 = (b1 - tmp) / (2 * a);
    y2 = k * x2 + b;
    return [x1, y1, x2, y2];
  }
}

// 竖屏判断8个方位 - 正常点位
function getDirection(pos) {
  var rad = Math.atan2(pos.y, pos.x); // [-PI, PI]
  if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
    // return cc.v2(1, 0); // 右
  } else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
    // return cc.v2(1, 1); // 右上
  } else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
    // return cc.v2(0, 1); // 上
  } else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
    // return cc.v2(-1, 1); // 左上
  } else if (
    (rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
    (rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
  ) {
    // return cc.v2(-1, 0); // 左
  } else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
    // return cc.v2(-1, -1); // 左下
  } else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
    // return cc.v2(0, -1); // 上
  } else {
    // return cc.v2(1, -1); // 右下
  }
}

var sCallback;
Joystick.prototype._addCallback = function (fn) {
  sCallback = fn;
};

export default Joystick;
demo.vue
<template>
    <div class="joystick-content">
      <canvas id="joystick"
              width="150"
              height="150"></canvas>
    </div>
</template>
<script>
import Joystick from "../plugins/Joystick.js";
export default{
    mounted(){
        this.joystick = new Joystick("joystick");
        this.joystick._addCallback(this.joyCb);
    },
    methods: {
        joyCb(x, y) {
            // 不清楚为什么上下颠倒了,所以y轴乘于-1 调整回去
            y = y * -1;
            console.log(x, y, "joyCD");
            this.getDirection({ x, y })
        },
        getDirection(pos) {
            var rad = Math.atan2(pos.y, pos.x); // [-PI, PI]
            if ((rad >= -Math.PI / 8 && rad < 0) || (rad >= 0 && rad < Math.PI / 8)) {
                // (1, 0); // 右
                console.log("右");
            } else if (rad >= Math.PI / 8 && rad < (3 * Math.PI) / 8) {
                // (1, 1); // 右上
                console.log("右上");
            } else if (rad >= (3 * Math.PI) / 8 && rad < (5 * Math.PI) / 8) {
                // (0, 1); // 上
                console.log("上");
            } else if (rad >= (5 * Math.PI) / 8 && rad < (7 * Math.PI) / 8) {
                // (-1, 1); // 左上
                console.log("左上");
            } else if (
            (rad >= (7 * Math.PI) / 8 && rad < Math.PI) ||
            (rad >= -Math.PI && rad < (-7 * Math.PI) / 8)
            ) {
                // (-1, 0); // 左
                console.log("左");
            } else if (rad >= (-7 * Math.PI) / 8 && rad < (-5 * Math.PI) / 8) {
                // (-1, -1); // 左下
                console.log("左下");
            } else if (rad >= (-5 * Math.PI) / 8 && rad < (-3 * Math.PI) / 8) {
                // (0, -1); // 上
                console.log("上");
            } else {
                // (1, -1); // 右下
                console.log("右下");
            }
            const data = {
                x: (pos.x / 30.0).toFixed(2),
                y: (pos.x / 30.0).toFixed(2),
            };
        },
    }
}
</script>