canvas裁剪图片demo

本文详细介绍了如何使用HTML5 Canvas实现图片裁剪功能,包括监听鼠标事件实现裁剪框移动,计算裁剪坐标,以及将裁剪后的canvas转换为image。示例代码展示了从选择图片到裁剪、导出的完整流程,适用于前端开发中图片编辑场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

需求

  • 支持本地选择图片绘制到画布
  • 移动裁剪框 ,裁剪固定宽高的图片
  • 支持导出canvasimage

难点

  • 裁剪框移动需要监听onmousedown,onmousemove,onmouseup。移动限定边界,
  • 鼠标坐标需使用pageX,pageY。使用offsetXoffsetY是不准确的,有可能出现(0,0)的情况,会导致移动闪烁,通过计算可以得出鼠标在画布中的偏移坐标类似于offsetX,offsetY
  • 需要根据鼠标点坐标,计算裁剪框的坐标,需要分析鼠标点坐标与裁剪框宽高的关系。

效果

在这里插入图片描述

Html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="./style.css" />
    <title>canvas-cut-image</title>
  </head>
  <body>
    <div class="container">
      <div class="toolbar">
        <button id="choose" class="btn">选择图片</button>
        <button id="begin-cut" class="btn primary">开始裁剪</button>
        <button id="finish" class="btn success">完成</button>
        <button id="export" class="btn success">导出</button>
        <input
          type="file"
          name="file-inp"
          id="file-inp"
          accept=".jpg, .jpeg, .png"
        />
      </div>
      <div class="canvas-wrap">
        <canvas id="myCanvas"> </canvas>
        <div class="cut-box"></div>
      </div>
      <div id="preview"></div>
    </div>
    <script src="./script.js"></script>
  </body>
</html>

Script

const selectImgBtn = document.querySelector("#choose") // 选择按钮
const beginCutBtn = document.querySelector("#begin-cut") // 开始裁剪按钮
const finishBtn = document.querySelector("#finish") // 完成按钮
const exportBtn = document.querySelector("#export") // 导出按钮
const fileInp = document.querySelector("#file-inp") // 上传器
const preview = document.querySelector("#preview") // 容器
const canvasWrap = document.querySelector(".canvas-wrap") // 画布包裹层
const canvas = document.querySelector("#myCanvas") // 画布
const cutBox = document.querySelector(".cut-box") // 裁剪框
const canvas_init_width = 1200 // 画布初始宽
const canvas_init_height = 800 // 画布初始高
const cutBox_init_width = 375 // 裁剪框初始宽
const cutBox_init_height = 200 // 裁剪框初始高
let ctx = null // 画布上下文
let sourceImg = null // 源文件
let canvasImg = null // 画布图片
let isCutting = false // 正在裁剪标记
let mouse_x = null // 鼠标坐标x
let mouse_y = null // 鼠标坐标y
let cutBox_x = 0 // 裁剪框坐标x
let cutBox_y = 0 // 裁剪宽坐标y
init()
// 监听选择图片
selectImgBtn.addEventListener("click", () => {
  fileInp.click()
})
// 监听文件输入
fileInp.addEventListener("change", (event) => {
  sourceImg = event.target.files[0]
  drawImageToCanvas(sourceImg)
})
// 监听开始裁剪
beginCutBtn.addEventListener("click", () => {
  if (!isCutting) {
    isCutting = true
    cutBox.style.display = "block"
  }
})
// 监听完成裁剪
finishBtn.addEventListener("click", () => {
  isCutting = false
  cutBox.style.display = "none"
  beginCutImage()
  resetCutBoxPosition()
})
// 监听导出
exportBtn.addEventListener("click", () => {
  preview.innerHTML = ""
  preview.appendChild(convertCanvasToImage(canvas))
})
// 监听裁剪框鼠标按下
cutBox.addEventListener("mousedown", () => {
  canvasWrap.onmousemove = (e) => {
    // 这里必须用pageX,pageY才是准确的,否则会导致页面频闪。
    mouse_x = e.pageX - 300 // 300 是marginLeft
    mouse_y = e.pageY - 100 // 100 是toolbar的高
    // console.log(e)
    // 裁剪框的坐标x = 鼠标偏移坐标x - (裁剪框宽度/2)
    cutBox_x = mouse_x - cutBox_init_width / 2
    // 裁剪框的坐标y = 鼠标偏移坐标y - (裁剪框高度/2)
    cutBox_y = mouse_y - cutBox_init_height / 2
    // 保持鼠标永远在裁剪框中央
    if (mouse_x < cutBox_init_width / 2) {
      // 左边界:鼠标偏移坐标x > (裁剪框宽度/2)
      cutBox_x = 0
    } else if (mouse_x > canvas_init_width - cutBox_init_width / 2) {
      // 右边界:鼠标偏移坐标x < 画布宽 - (裁剪框宽度/2)
      cutBox_x = canvas_init_width - cutBox_init_width
    }

    if (mouse_y < cutBox_init_height / 2) {
      // 上边界:鼠标偏移坐标y > (裁剪框高度/2)
      cutBox_y = 0
    } else if (mouse_y > canvas_init_height - cutBox_init_height / 2) {
      // 下边界:鼠标偏移坐标y < 画布高 - (裁剪框高度/2)
      cutBox_y = canvas_init_height - cutBox_init_height
    }

    cutBox.style.top = `${cutBox_y}px`
    cutBox.style.left = `${cutBox_x}px`
  }

  // 监听鼠标弹起
  cutBox.addEventListener("mouseup", () => {
    canvasWrap.onmousemove = null
    console.log("鼠标弹起不再监听鼠标移动")
  })
})
// 初始化画布和裁剪框
function init() {
  ctx = canvas.getContext("2d")
  canvas.width = canvas_init_width
  canvas.height = canvas_init_height
  cutBox.style.width = `${cutBox_init_width}px`
  cutBox.style.height = `${cutBox_init_height}px`
}
// 把图片画进画布
function drawImageToCanvas(file) {
  clearCanvas()
  canvasImg = new Image()
  canvasImg.onload = () => {
    ctx.drawImage(
      canvasImg,
      0,
      0,
      canvasImg.width < canvas_init_width ? canvasImg.width : canvas_init_width,
      canvasImg.height < canvas_init_height
        ? canvasImg.height
        : canvas_init_height
    )
  }
  canvasImg.src = getObjectURL(file)
}
// 生成本地图片URL地址
function getObjectURL(file) {
  let url = null
  if (window.createObjectURL !== undefined) {
    // basic
    url = window.createObjectURL(file)
  } else if (window.webkitURL !== undefined) {
    // webkit or chrome
    url = window.webkitURL.createObjectURL(file)
  } else if (window.URL !== undefined) {
    // mozilla(firefox)
    url = window.URL.createObjectURL(file)
  }
  return url
}
// 清空画布
function clearCanvas() {
  ctx.clearRect(0, 0, canvas_init_width, canvas_init_height)
}
// 重置裁剪框位置
function resetCutBoxPosition() {
  mouse_x = null
  mouse_y = null
  cutBox_x = 0
  cutBox_y = 0
  cutBox.style.top = `${cutBox_y}px`
  cutBox.style.left = `${cutBox_x}px`
}
// 裁剪图片
function beginCutImage() {
  clearCanvas()
  // https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/drawImage#%E7%A4%BA%E4%BE%8B
  ctx.drawImage(
    canvasImg,
    cutBox_x,
    cutBox_y,
    cutBox_init_width,
    cutBox_init_height,
    0,
    0,
    cutBox_init_width,
    cutBox_init_height
  )
}
// 画布图片
function convertCanvasToImage(canvas) {
  var image = new Image()
  image.src = canvas.toDataURL("image/png")
  return image
}

Style

body {
  margin: 0;
  padding: 0;
  background: #f5f5f5;
}

.container {
  width: 1200px;
  min-height: 100vh;
  margin: 0 auto;
  background: #fff;
}

.toolbar {
  width: 100%;
  padding: 30px;
}

button.btn {
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  background: #fff;
  border: 1px solid #dcdfe6;
  color: #606266;
  -webkit-appearance: none;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  margin: 0;
  transition: .1s;
  font-weight: 500;
  padding: 12px 20px;
  font-size: 14px;
  border-radius: 4px;
}

button.primary {
  color: #fff;
  background-color: #409eff;
  border-color: #409eff;
}

button.success {
  color: #fff;
  background-color: #67c23a;
  border-color: #67c23a;
}

.toolbar>button:active,
.toolbar>button:hover {
  filter: brightness(0.85);
}

.canvas-wrap {
  position: relative;
}

#myCanvas {
  width: 100%;
  height: 800px;
  background-color: #333;
}

.cut-box {
  display: none;
  position: absolute;
  top: 0;
  left: 0;
  width: 375px;
  height: 200px;
  background-color: rgba(255, 255, 255, .4);
  border: 1px solid #000;
  cursor: move;
}

#file-inp {
  display: none;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值