首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Canvas 圖片載入與滑鼠互動(旋轉、縮放、移動)完整實作

     編輯:程式設計綜合問答

需求

點選圖片 → 載入至 Canvas → 滑鼠懸停/拖曳控制圖片旋轉、縮放、平移。

核心概念

  • 仿射變換矩陣:以 3x3 矩陣(或 SVGMatrix / DOMMatrix)表示 translate、scale、rotate 組合。
  • 座標系轉換:滑鼠螢幕座標 → Canvas 座標 → 圖片區域座標(需乘以矩陣逆矩陣)。
  • 事件處理:mousedown/mousemove/mouseup/wheel。
  • 重繪迴圈:requestAnimationFrame 或事件觸發後 draw()。

完整可執行範例(原生 JS,無依賴)

<!DOCTYPE html>
<html><head><meta charset="utf-8"><style>
  body { margin:0; overflow:hidden; background:#222; }
  canvas { display:block; cursor:grab; background:#111; }
  canvas:active { cursor:grabbing; }
  #imgPicker { position:fixed; top:10px; left:10px; z-index:10; }
</style></head><body>
<input type="file" id="imgPicker" accept="image/*">
<canvas id="c"></canvas>
<script>
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
let img = null;
// 變換矩陣 [a, b, c, d, e, f] 對應 DOMMatrix 2D
let mat = [1, 0, 0, 1, 0, 0]; // identity
let dragging = false, lastX = 0, lastY = 0;

// 矩陣工具
const mMult = (m, n) => [
  m[0]*n[0] + m[1]*n[2], m[0]*n[1] + m[1]*n[3],
  m[2]*n[0] + m[3]*n[2], m[2]*n[1] + m[3]*n[3],
  m[4]*n[0] + m[5]*n[2] + n[4], m[4]*n[1] + m[5]*n[3] + n[5]
];
const mTranslate = (tx, ty) => [1,0,0,1,tx,ty];
const mScale = (sx, sy) => [sx,0,0,sy,0,0];
const mRotate = (ang) => { const c=Math.cos(ang), s=Math.sin(ang); return [c,s,-s,c,0,0]; };
const mInverse = (m) => {
  const det = m[0]*m[3] - m[1]*m[2];
  return [ m[3]/det, -m[1]/det, -m[2]/det, m[0]/det,
          (m[2]*m[5]-m[3]*m[4])/det, (m[1]*m[4]-m[0]*m[5])/det ];
};

// 將滑鼠座標轉為圖片局部座標
function toLocal(x, y) {
  const inv = mInverse(mat);
  return { x: inv[0]*x + inv[2]*y + inv[4], y: inv[1]*x + inv[3]*y + inv[5] };
}

// 重繪
function draw() {
  const w = canvas.width = innerWidth, h = canvas.height = innerHeight;
  ctx.setTransform(1,0,0,1,0,0);
  ctx.clearRect(0,0,w,h);
  if (!img) return;
  ctx.setTransform(...mat);
  ctx.drawImage(img, 0, 0);
}

// 事件
canvas.addEventListener('mousedown', e => {
  dragging = true; lastX = e.clientX; lastY = e.clientY;
  canvas.style.cursor = 'grabbing';
});
window.addEventListener('mousemove', e => {
  if (!dragging) return;
  const dx = e.clientX - lastX, dy = e.clientY - lastY;
  lastX = e.clientX; lastY = e.clientY;
  // 平移:在當前變換下平移
  mat = mMult(mTranslate(dx, dy), mat);
  draw();
});
window.addEventListener('mouseup', () => { dragging = false; canvas.style.cursor = 'grab'; });
canvas.addEventListener('wheel', e => {
  e.preventDefault();
  const scale = e.deltaY > 0 ? 0.9 : 1.1;
  // 以滑鼠位置為中心縮放:T⁻¹ * S * T
  const local = toLocal(e.clientX, e.clientY);
  mat = mMult(mMult(mMult(mTranslate(local.x, local.y), mScale(scale, scale)), mTranslate(-local.x, -local.y)), mat);
  draw();
}, {passive:false});

// 載入圖片
document.getElementById('imgPicker').addEventListener('change', e => {
  const file = e.target.files[0]; if (!file) return;
  const url = URL.createObjectURL(file);
  img = new Image();
  img.onload = () => {
    URL.revokeObjectURL(url);
    // 置中且適應畫面
    const scale = Math.min(innerWidth/img.width, innerHeight/img.height) * 0.9;
    mat = [scale,0,0,scale, (innerWidth-img.width*scale)/2, (innerHeight-img.height*scale)/2];
    draw();
  };
  img.src = url;
});

// 處理視窗縮放
window.addEventListener('resize', draw);
</script></body></html>

關鍵點解析

  1. 矩陣乘法順序:新變換左乘舊矩陣(mat = mMult(newT, mat)),符合「先套用新變換,再套用舊變換」的語意。
  2. 以滑鼠為中心縮放/旋轉:需先將原點移至該點(T⁻¹),套用縮放/旋轉,再移回(T),即 T * S * T⁻¹。
  3. 點擊測試:若需判斷滑鼠是否在圖片上,使用 toLocal() 後檢查 0 <= x < img.width && 0 <= y < img.height。
  4. 效能:僅在變更時呼叫 draw(),避免持續 requestAnimationFrame。

延伸:使用成熟庫

  • Fabric.js:canvas.add(new fabric.Image(img)) 內建旋轉/縮放/移動控制點。
  • Konva.js:高效能 2D 渲染,支援拖曳、變換器 Transformer。
  • Canvas API + CSS transform:將 <img> 置於 <div>,用 CSS transform: matrix(...),效能極佳但需自行處理座標映射。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved