Taro 小程序 canvas 图片裁剪案例

770 阅读1分钟
<template>
  <canvas
    canvas-id="canvasImage"
    id="canvasImage"
    :disableScroll="true"
  ></canvas>
  <canvas
    canvas-id="canvasBackground"
    id="canvasBackground"
    :disableScroll="true"
    @error="onError"
    @touchstart="onTouchStart"
    @touchmove="onTouchMove"
    @touchend="onTouchend"
  >
    <cover-view class="canvas-bottom"
      ><cover-view @tap="onChooseImage">选择图片</cover-view
      ><cover-view @tap="onCanvasToTempFilePath">确认</cover-view></cover-view
    >
  </canvas>
</template>

<script lang="ts" setup>
import {
  eventCenter,
  getCurrentInstance,
  createCanvasContext,
  getSystemInfoSync,
  chooseImage,
  getImageInfo,
  canvasToTempFilePath,
} from "@tarojs/taro";
import { reactive, ref } from "vue-demi";
// 当前设备宽高数据的接口
interface GetSystem {
  screenHeight: number; //宽
  screenWidth: number; //高
}
// 当前选择的遮罩的宽高和位置
interface ChoiceBox {
  w: number; // 选择框的宽
  h: number; // 选择框的高
  t: number; // 选择框的头部位置
  l: number; // 选择框的左边的位置
}
// 要绘制的图片
interface DrawImageMessage {
  path: string; //图片的路径
  width: number; //图片的实际宽度
  height: number; //图片的实际高度
  canvasw: number; //图片绘制在canvas的宽度
  canvash: number; //图片绘制在canvas的高度
  ratio: number; //图片的宽高比例
  y: number; //图片的y轴距离
  x: number; //图片的x轴距离
  zoomw: number; // 图片缩放的宽
  zoomh: number; // 图片缩放的高
  dragx: number; //拖动产生的,x轴渲染位置
  dragy: number; //拖动产生的,y轴渲染位置
}
// 记录单手指拖动
interface OneFingerDrag {
  x: number; //拖动的x轴
  y: number; //拖动的y轴
}
let one_finger_drag = reactive<OneFingerDrag>({
  x: 0,
  y: 0,
});
// 当前设备宽高数据
let getSystemInfoSync_data = reactive<GetSystem>({
  screenHeight: 0,
  screenWidth: 0,
});
// 当前选择的遮罩的宽高和位置
let choice_box = reactive<ChoiceBox>({
  w: 250,
  h: 250,
  t: 0,
  l: 0,
});
let draw_image_message = reactive<DrawImageMessage>({
  path: "", //图片的路径
  width: 0, //图片的实际宽度
  height: 0, //图片的实际高度
  canvasw: 0, //图片绘制在canvas的宽度
  canvash: 0, //图片绘制在canvas的高度
  ratio: 0, //图片的宽高比例
  y: 0, //图片渲染位置
  x: 0, //图片渲染位置
  zoomw: 0, // 图片缩放的宽
  zoomh: 0, // 图片缩放的高
  dragx: 0, //拖动产生的,x轴渲染位置
  dragy: 0, //拖动产生的,y轴渲染位置
});
let initialPosition = ref<number>(0); //手指的初始距离
let record_left = ref<number>(0); //记录当前手指松开次数

// 图片选择
const onChooseImage = (): void => {
  chooseImage({
    count: 1, //默认9
    sizeType: ["original", "compressed"], //可以指定是原图还是压缩图,默认二者都有
    sourceType: ["album", "camera"], //从相册选择
    success: (res) => {
      getImageInfo({
        src: res.tempFilePaths[0],
        success: (image) => {
          let { width, height, path } = image;

          draw_image_message.width = width; //保存当前图片的实际宽度
          draw_image_message.ratio = width / height; //保存当前图片的宽高比例
          draw_image_message.height = height; //保存当前图片的实际高度
          draw_image_message.path = path; //保存当前图片的路径
          draw_image_message.canvash = draw_image_message.zoomh =
            getSystemInfoSync_data.screenWidth / draw_image_message.ratio; //保存当前图片的渲染高度和缩放高度
          draw_image_message.canvasw = draw_image_message.zoomw =
            getSystemInfoSync_data.screenWidth; //保存当前图片的渲染宽度和缩放宽度

          draw_image_message.x = 0; //保存当前图片的渲染 x轴的位置
          draw_image_message.y =
            getSystemInfoSync_data.screenHeight / 2 -
            draw_image_message.canvash / 2; //保存当前图片的渲染 y轴的位置

          canvasImage.drawImage(
            //渲染图片
            path,
            draw_image_message.x,
            draw_image_message.y,
            draw_image_message.canvasw,
            draw_image_message.canvash
          );
        },
      });
    },
  });
};
// 导出要截取的图片
const onCanvasToTempFilePath = () => {
  canvasToTempFilePath({
    canvasId: "canvasImage",
    destHeight: choice_box.h,
    destWidth: choice_box.w,
    height: choice_box.h,
    width: choice_box.w,
    x: choice_box.l,
    y: choice_box.t,
    success(res) {
      console.log(res);
    },
    fail(err) {
      console.log(err);
    },
  });
};

eventCenter.once(getCurrentInstance().router.onReady, () => {
  // 获取当前设备的宽高 -s
  let getSystem = getSystemInfoSync();
  getSystemInfoSync_data.screenWidth = getSystem.screenWidth;
  getSystemInfoSync_data.screenHeight = getSystem.screenHeight;
  // 获取当前设备的宽高 -s
  // 计算选择框的x和y的位置 -s
  choice_box.t = getSystem.screenHeight / 2 - choice_box.w / 2; //如果设置了navigationStyle: 'custom', 不需要后面的减法
  choice_box.l = getSystem.screenWidth / 2 - choice_box.h / 2;
  // 计算选择框的x和y的位置 -s
  canvasBackground.init();
  canvasImage.init();
});

// 绘制图片
let canvasImage = {
  instantiation: null,
  init(): void {
    this.instantiation = createCanvasContext("canvasImage");
  },
  drawImage(
    path: string,
    dx: number = 0,
    dy: number = 0,
    dWidth: number = 0,
    dHeight: number = 0
  ): void {
    this.instantiation.drawImage(path, dx, dy, dWidth, dHeight);
    this.instantiation.draw();
  },
};

// 绘制背景图的 canvas 组件 -s
let canvasBackground = {
  instantiation: null,
  init(): void {
    this.instantiation = createCanvasContext("canvasBackground");
    this.fillAvatarCropperBgLucencyColor();
  },
  // 绘制透明遮罩层
  fillAvatarCropperBgLucencyColor(): void {
    this.instantiation.setFillStyle("rgba(0,0,0,.2)");
    this.instantiation.fillRect(
      0,
      0,
      getSystemInfoSync_data.screenWidth,
      choice_box.t
    );
    this.instantiation.fillRect(
      0,
      choice_box.t,
      choice_box.l,
      getSystemInfoSync_data.screenHeight
    );
    this.instantiation.fillRect(
      choice_box.l + choice_box.w,
      choice_box.t,
      choice_box.l,
      getSystemInfoSync_data.screenHeight
    );
    this.instantiation.fillRect(
      choice_box.l,
      choice_box.t + choice_box.w,
      choice_box.w,
      getSystemInfoSync_data.screenHeight
    );
    this.instantiation.save();
    this.instantiation.draw({ reserve: true });
    this.strokeRect();
  },
  // 绘制一个矩形
  strokeRect(): void {
    this.instantiation.setStrokeStyle("#fff");
    this.instantiation.strokeRect(
      choice_box.l,
      choice_box.t,
      choice_box.w,
      choice_box.h
    );
    this.instantiation.draw({ reserve: true });
  },
};
// 绘制背景图的 canvas 组件 -e

// 操作
//错误信息
const onError = (err: any): void => {
  console.log(err);
};
// 	手指触摸动作结束
const onTouchend = (): void => {
  // 保存拖动后的值
  draw_image_message.x = draw_image_message.dragx;
  draw_image_message.y = draw_image_message.dragy;

  // 记录当前手指松开次数,用于判断当前的操作是缩放还是拖动,避免,缩放后,最后松开手指时,会照成拖动动作
  record_left.value += 1;
  if (initialPosition.value > 0) {
    if (record_left.value === 2) {
      // 清除两个手指移动的距离
      initialPosition.value = 0;
      record_left.value = 0;
    }
  } else {
    record_left.value = 0;
  }
};
// 手指触摸动作开始
const onTouchStart = (e: any): void => {
  if (
    start.hasOwnProperty("touch" + e.touches.length) &&
    draw_image_message.path.length > 1
  ) {
    // 分配单手指和双手指的处理方式
    start["touch" + e.touches.length](e);
  }
};
// 手指拖动
const onTouchMove = (e: any): void => {
  if (
    move.hasOwnProperty("touch" + e.touches.length) &&
    draw_image_message.path.length > 1
  ) {
    // 分配单手指和双手指的处理方式
    move["touch" + e.touches.length](e);
  }
};
// 计算两个手指之间的距离
const getNewScale = (touch0: any, touch1: any): number => {
  let xMove: number, yMove: number;
  // 计算两指距离
  xMove = Math.round(touch1.x - touch0.x);
  yMove = Math.round(touch1.y - touch0.y);
  return Math.round(Math.sqrt(xMove * xMove + yMove * yMove));
};

// 手指的处理方式
interface Touch {
  touch1: (e: any) => void; //单手指拖动处理
  touch2: (e: any) => void; //双手指拖动处理
}
// 手指的拖动的处理方式
const move: Touch = {
  /**
   * 双手指拖动处理
   * @param e 事件模型
   */
  touch2(e: any): void {
    let newDistance: number = getNewScale(e.touches[0], e.touches[1]);
    let coefficient: number =
      1.5 + 0.001 * 8 * (newDistance - initialPosition.value);
    //  设定缩放范围
    coefficient <= 1 && (coefficient = 1);
    coefficient >= 2.5 && (coefficient = 2.5);
    let imagew: number = Math.round(coefficient * draw_image_message.canvasw);
    let imageh: number = Math.round(coefficient * draw_image_message.canvash);

    // 计算图片渲染位置
    let x: number = draw_image_message.x;
    let y: number = draw_image_message.y;

    // 保存当前图片的渲染宽高
    draw_image_message.zoomw = imagew;
    draw_image_message.zoomh = imageh;

    // 计算图片x轴的范围
    if (x + draw_image_message.zoomw <= choice_box.l + choice_box.w) {
      x = choice_box.l + choice_box.w - draw_image_message.zoomw;
    }

    // 计算图片y轴的范围
    if (y + draw_image_message.zoomh <= choice_box.h + choice_box.t) {
      y = choice_box.h + choice_box.t - draw_image_message.zoomh;
    }

    // 保存当前图片渲染的位置
    draw_image_message.dragx = x;
    draw_image_message.dragy = y;

    // 绘制图片
    canvasImage.drawImage(
      draw_image_message.path,
      x,
      y,
      draw_image_message.zoomw,
      draw_image_message.zoomh
    );
  },
  /**
   * 单手指拖动处理
   * @param e 事件模型
   */
  touch1(e: any): void {
    if (initialPosition.value > 0) return; // 如果当前initialPosition值大于零,表示当前是两个手指在做缩放动作
    let { x, y } = e.touches[0];
    let dx = Math.round(draw_image_message.x - (one_finger_drag.x - x));
    let dy = Math.round(draw_image_message.y - (one_finger_drag.y - y));
    // 判断图片在 x轴移动的位置限制
    if (dx >= choice_box.l) {
      dx = choice_box.l;
    } else if (dx <= choice_box.l + choice_box.w - draw_image_message.zoomw) {
      dx = choice_box.l + choice_box.w - draw_image_message.zoomw;
    }
    // 判断图片在y轴移动的位置判断
    if (dy >= choice_box.t) {
      dy = choice_box.t;
    } else if (dy <= choice_box.t + choice_box.h - draw_image_message.zoomh) {
      dy = choice_box.t + choice_box.h - draw_image_message.zoomh;
    }

    // 保存移动后图片渲染的位置
    draw_image_message.dragx = dx;
    draw_image_message.dragy = dy;

    //  绘制图片
    canvasImage.drawImage(
      draw_image_message.path,
      dx,
      dy,
      draw_image_message.zoomw,
      draw_image_message.zoomh
    );
  },
};
// 手指初始化位置
const start: Touch = {
  /**
   * 单手指处理
   * @param e 事件模型
   */
  touch1(e: any): void {
    one_finger_drag.x = e.touches[0].x;
    one_finger_drag.y = e.touches[0].y;
  },
  /**
   * 双手指处理
   * @param e 事件模型
   */
  touch2(e: any): void {
    let oldDistance: number = getNewScale(e.touches[0], e.touches[1]);
    initialPosition.value = oldDistance;
  },
};
</script>

<style>
page {
  background-color: #000;
}
</style>
<style lang="scss">
#canvasBackground,
#canvasImage {
  width: 100vw;
  height: 100vh;
  position: absolute;
  top: 0;
  left: 0;
  z-index: 11;
}
.canvas-bottom {
  width: calc(100vw - 40rpx);
  height: 100rpx;
  background-color: #000;
  position: fixed;
  bottom: 0;
  left: 0;
  color: #ffffff;
  padding: 0 20rpx;
  display: flex;
  justify-content: flex-start;
  align-items: center;
  justify-content: space-between;
}
</style>