실무/vue.js 실무 기능

13. Canvas로 이미지 확대,축소,이동 구현

DEV-Front 2023. 11. 20. 18:11
반응형
<template>
  <div id="canvasCon" ref="imageViewRef">
    <canvas id='zoomCanvas' :width=userSettingCanvasWidth :height=userSettingCanvasHeight @mousedown='handleMouseDown'
      @mousemove='handleMouseMove' @mouseup='handleMouseUp' @wheel='handleWheel'
      style="border: 1px solid grey; border-radius: 4px;"></canvas>
  </div>
</template>
<script setup>
import { fabric } from 'fabric';
import { reactive, onMounted, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
const router = useRouter();

let canvas = null;
let context = null;
const img = new Image();

// viewerInfo
const userSettingCanvasWidth = ref(null);
const userSettingCanvasHeight = ref(null);
// viewerInfo END

let widthRatio = null;
let heightRatio = null;
let x = 0;
let y = 0;

let newScale = null;

const scale = ref(1);
const MIN_SCALE = ref(0.1);
const MAX_SCALE = ref(100);
const panning = ref(false);
let viewPos = reactive({ x: 0, y: 0 });
let startPos = reactive({ x: 0, y: 0 });

const props = defineProps({
  imageUrl: { type: String, required: true },
  viewerInfo: { type: Object, required: true, width: 0, height: 0 }
});

const tempW = ref(0);
const tempH = ref(0);

onMounted(() => {
  window.addEventListener('resize', handleResize);

  userSettingCanvasWidth.value = props.viewerInfo.width;
  userSettingCanvasHeight.value = props.viewerInfo.height;

  // 기본 창 사이즈 기준(1920 x 937) 으로 유지할 padding 값 구하기
  tempW.value = 1920 - userSettingCanvasWidth.value;
  tempH.value = 937 - userSettingCanvasHeight.value;

  userSettingCanvasWidth.value = window.innerWidth - tempW.value;
  userSettingCanvasHeight.value = window.innerHeight - tempH.value;
}
);

const handleResize = () => {
  userSettingCanvasWidth.value = window.innerWidth - tempW.value;
  userSettingCanvasHeight.value = window.innerHeight - tempH.value;

  img.src = props.imageUrl;
  ratioSetting();
};

watch(() => props.imageUrl, () => {
  if (context !== null) context.reset();
  img.src = props.imageUrl;
});

img.onload = function () {
  firstDraw();
};

const firstDraw = () => {
  // canvas = new fabric.Canvas('zoomCanvas');
  canvas = document.getElementById('zoomCanvas');
  console.log(canvas);
  startPos.x = 0;
  startPos.y = 0;

  ratioSetting();

  context = canvas.getContext('2d', { colorSpace: 'srgb' });

  context.drawImage(img, x, y, widthRatio, heightRatio);
};

const ratioSetting = () => {
  const canvasWidth = canvas.width;
  const canvasHeight = canvas.height;
  const imgWidth = img.width;
  const imgHeight = img.height;

  if (imgWidth > imgHeight) {
    heightRatio = canvasHeight; // 이미지의 세로를 플랫폼으로 맞추고
    widthRatio = canvasHeight * (imgWidth / imgHeight); // 세로에따른 비율계산
    x = (canvasWidth / 2) - widthRatio / 2;
  } else if (imgHeight > imgWidth) {
    widthRatio = canvasWidth; // 이미지의 가로를 플랫폼으로 맞추고
    heightRatio = canvasWidth * (imgHeight / imgWidth); // 가로에따른 비율계산
    y = (canvasHeight / 2) - heightRatio / 2;
  } else { // 이미지 가로, 세로 길이 같을때
    widthRatio = canvasHeight * (imgWidth / imgHeight); // 이미지의 가로를 플랫폼으로 맞추고
    heightRatio = canvasHeight * (imgHeight / imgWidth); // 가로에따른 비율계산
    x = (canvasWidth / 2) - widthRatio / 2;
  }
};

const draw = () => {
  context = canvas.getContext('2d');

  context.setTransform(
    scale.value,
    0,
    0,
    scale.value,
    viewPos.x,
    viewPos.y
  );

  context.drawImage(img, x, y, widthRatio, heightRatio);
};

const handleMouseDown = (e) => { // canvas 선택
  const { offsetX, offsetY } = e;
  e.preventDefault();

  startPos = {
    x: offsetX - viewPos.x,
    y: offsetY - viewPos.y
  };

  panning.value = true;
};

const handleMouseUp = (e) => { // canvas 선택 해제
  e.preventDefault();
  panning.value = false;
};

const handleMouseMove = (e) => { // canvas 이동
  const { offsetX, offsetY } = e;
  e.preventDefault();

  if (context === null) return;
  if (!panning.value) return;

  viewPos = {
    x: offsetX - startPos.x,
    y: offsetY - startPos.y
  };

  context.reset();
  draw();
};

const handleWheel = (e) => { // canvas 확대
  const { offsetX, offsetY } = e;
  e.preventDefault();

  if (context === null) return;

  const xs = (offsetX - viewPos.x) / scale.value;
  const ys = (offsetY - viewPos.y) / scale.value;
  const delta = -e.deltaY; // 사용자 마우스 휠 조작 이벤트 캐치
  newScale = delta > 0 ? scale.value * 1.2 : scale.value / 1.2; // 양수일시 1.2배 확대 음수일시 1.2배 축소

  if (newScale.toFixed(1) < 1) { // 이미지 원본사이즈 이상으로는 축소 못하게
    return;
  }

  if (newScale >= MIN_SCALE.value && newScale <= MAX_SCALE.value) {
    scale.value = newScale;
    viewPos = {
      x: offsetX - xs * scale.value,
      y: offsetY - ys * scale.value
    };

    context.reset();
    draw();
  }
};

</script>
<style>
#zoomCanvas {
  z-index: 1 !important;
  cursor: grab;
 
}

</style>

 

반응형