前端canvas中物体边框和控制点的实现示例

yizhihongxing

下面我来详细讲解一下 “前端canvas中物体边框和控制点的实现示例” 的完整攻略。

简介

在前端开发中,我们可能需要在canvas中绘制一些图形或者物体,同时需要提供控制点以方便用户进行交互。此时,我们就需要实现物体边框和控制点,使得用户可以通过拖动控制点来对物体进行移动、旋转、缩放等操作。本文将详细介绍这个过程的实现。

实现步骤

  1. 绘制物体

在canvas中绘制物体的代码,可以使用路径(Path)的方式进行,具体步骤如下:

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
// ...
ctx.closePath();
ctx.fill(); // 填充物体内部颜色
  1. 绘制边框

在绘制物体的基础上,我们需要绘制物体的边框,以便于用户进行交互。在canvas中,我们可以通过绘制一个矩形作为边框,也可以通过路径的方式绘制出物体的轮廓。

对于路径的方式,我们可以通过以下的代码实现:

ctx.beginPath();
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.lineTo(x3, y3);
// ...
ctx.closePath();
ctx.stroke(); // 绘制路径线条
  1. 绘制控制点

控制点是用户进行交互的关键。在canvas中,我们可以绘制一个圆形作为控制点,然后在用户鼠标移动到该圆形范围内时,修改鼠标的样式以方便提示用户进行交互。

绘制圆形的代码如下:

ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill(); // 填充圆形内部颜色
  1. 实现交互

用户在鼠标移动到控制点的范围内时,我们需要修改鼠标样式以提示用户可以进行交互。

canvas.style.cursor = 'move'; // 修改鼠标样式为移动

用户按下鼠标时,我们需要记录下鼠标的位置和物体的位置,以便于之后的移动操作。

在鼠标拖拽的过程中,我们需要计算出鼠标的偏移量,然后对物体进行相应的移动、缩放、旋转等操作。

  1. 示例说明

下面提供两个示例来说明以上实现步骤:

示例1:绘制矩形,并实现拖动和缩放

下面是实现代码:

// 绘制矩形
ctx.fillStyle = '#ff0000';
ctx.fillRect(50, 50, 100, 100);

// 绘制边框
ctx.strokeStyle = '#0000ff';
ctx.lineWidth = 1;
ctx.strokeRect(50, 50, 100, 100);

// 绘制控制点
const radius = 5;
ctx.fillStyle = '#0000ff';
ctx.beginPath();
ctx.arc(50, 50, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(100, 50, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(100, 100, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(50, 100, radius, 0, Math.PI * 2);
ctx.fill();

// 添加交互事件
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);

// 实现拖动和缩放
let isDragging = false;
let isResizing = false;
let offsetX = 0;
let offsetY = 0;
let offsetWidth = 0;
let offsetHeight = 0;

function handleMouseDown(e) {
  const x = e.pageX - canvas.offsetLeft;
  const y = e.pageY - canvas.offsetTop;

  if (isPointInCircle(x, y, 50, 50, radius)) {
    isDragging = true;
    offsetX = x - 50;
    offsetY = y - 50;
  } else if (isPointInCircle(x, y, 100, 50, radius)) {
    isResizing = true;
    offsetX = x - 100;
    offsetY = y - 50;
    offsetWidth = 100 - 50;
    offsetHeight = 100 - 50;
  } else if (isPointInCircle(x, y, 100, 100, radius)) {
    isResizing = true;
    offsetX = x - 100;
    offsetY = y - 100;
    offsetWidth = 100 - 50;
    offsetHeight = 100 - 50;
  } else if (isPointInCircle(x, y, 50, 100, radius)) {
    isResizing = true;
    offsetX = x - 50;
    offsetY = y - 100;
    offsetWidth = 100 - 50;
    offsetHeight = 100 - 50;
  } else {
    isDragging = false;
    isResizing = false;
  }
}

function handleMouseMove(e) {
  const x = e.pageX - canvas.offsetLeft;
  const y = e.pageY - canvas.offsetTop;

  if (isDragging) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#ff0000';
    ctx.fillRect(x - offsetX, y - offsetY, 100, 100);

    ctx.strokeStyle = '#0000ff';
    ctx.lineWidth = 1;
    ctx.strokeRect(x - offsetX, y - offsetY, 100, 100);

    ctx.fillStyle = '#0000ff';
    ctx.beginPath();
    ctx.arc(x - offsetX, y - offsetY, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x - offsetX + 100, y - offsetY, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x - offsetX + 100, y - offsetY + 100, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x - offsetX, y - offsetY + 100, radius, 0, Math.PI * 2);
    ctx.fill();
  } else if (isResizing) {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.fillStyle = '#ff0000';
    ctx.fillRect(50, 50, x - offsetX - 50 + offsetWidth, y - offsetY - 50 + offsetHeight);

    ctx.strokeStyle = '#0000ff';
    ctx.lineWidth = 1;
    ctx.strokeRect(50, 50, x - offsetX - 50 + offsetWidth, y - offsetY - 50 + offsetHeight);

    ctx.fillStyle = '#0000ff';
    ctx.beginPath();
    ctx.arc(50, 50, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x - offsetX + offsetWidth, 50, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(x - offsetX + offsetWidth, y - offsetY + offsetHeight, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(50, y - offsetY + offsetHeight, radius, 0, Math.PI * 2);
    ctx.fill();
  } else {
    if (isPointInCircle(x, y, 50, 50, radius) || isPointInCircle(x, y, 100, 50, radius) || isPointInCircle(x, y, 100, 100, radius) || isPointInCircle(x, y, 50, 100, radius)) {
      canvas.style.cursor = 'pointer';
    } else {
      canvas.style.cursor = 'default';
    }
  }
}

function handleMouseUp(e) {
  isDragging = false;
  isResizing = false;
}

function isPointInCircle(x, y, cx, cy, r) {
  const dx = x - cx;
  const dy = y - cy;
  const d = Math.sqrt(dx * dx + dy * dy);
  return d <= r;
}

在示例1中,我们实现了一个矩形的拖动和缩放操作。用户可以拖动矩形中的任意一个点进行移动,也可以拖动边框的任意一个角点进行缩放。

示例2:绘制三角形,并实现旋转和缩放

下面是实现代码:

// 绘制三角形
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.moveTo(100, 50);
ctx.lineTo(50, 150);
ctx.lineTo(150, 150);
ctx.closePath();
ctx.fill();

// 绘制边框
ctx.strokeStyle = '#0000ff';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(100, 50);
ctx.lineTo(50, 150);
ctx.lineTo(150, 150);
ctx.closePath();
ctx.stroke();

// 绘制控制点
ctx.fillStyle = '#0000ff';
ctx.beginPath();
ctx.arc(100, 50, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(50, 150, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 150, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(100, 150, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(75, 100, radius, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(125, 100, radius, 0, Math.PI * 2);
ctx.fill();

// 添加交互事件
canvas.addEventListener('mousedown', handleMouseDown);
canvas.addEventListener('mousemove', handleMouseMove);
canvas.addEventListener('mouseup', handleMouseUp);

// 实现旋转和缩放
let isRotating = false;
let isScaling = false;
let centerX = 100;
let centerY = 100;
let startAngle = 0;
let startScaleX = 1;
let startScaleY = 1;

function handleMouseDown(e) {
  const x = e.pageX - canvas.offsetLeft;
  const y = e.pageY - canvas.offsetTop;

  if (isPointInCircle(x, y, 100, 50, radius)) {
    isRotating = true;
    centerX = 100;
    centerY = 100;
    startAngle = Math.atan2(y - centerY, x - centerX);
  } else if (isPointInCircle(x, y, 50, 150, radius)) {
    isScaling = true;
    centerX = 100;
    centerY = 100;
    startScaleX = 150 - 100;
    startScaleY = 150 - 50;
  } else if (isPointInCircle(x, y, 150, 150, radius)) {
    isScaling = true;
    centerX = 100;
    centerY = 100;
    startScaleX = 50 - 100;
    startScaleY = 150 - 50;
  } else if (isPointInCircle(x, y, 100, 150, radius)) {
    isScaling = true;
    centerX = 100;
    centerY = 100;
    startScaleX = 1;
    startScaleY = Math.sqrt(Math.pow(startScaleX, 2) - Math.pow(50 - 100, 2));
  } else if (isPointInCircle(x, y, 75, 100, radius)) {
    isScaling = true;
    centerX = 100;
    centerY = 100;
    startScaleX = Math.sqrt(Math.pow(100 - 75, 2) + Math.pow(100 - 50, 2));
    startScaleY = Math.sqrt(Math.pow(100 - 125, 2) + Math.pow(100 - 50, 2));
  } else if (isPointInCircle(x, y, 125, 100, radius)) {
    isScaling = true;
    centerX = 100;
    centerY = 100;
    startScaleX = Math.sqrt(Math.pow(100 - 125, 2) + Math.pow(100 - 50, 2));
    startScaleY = Math.sqrt(Math.pow(100 - 75, 2) + Math.pow(100 - 50, 2));
  } else {
    isRotating = false;
    isScaling = false;
  }
}

function handleMouseMove(e) {
  const x = e.pageX - canvas.offsetLeft;
  const y = e.pageY - canvas.offsetTop;

  if (isRotating) {
    const angle = Math.atan2(y - centerY, x - centerX);
    const deltaAngle = angle - startAngle;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.save();
    ctx.translate(centerX, centerY);
    ctx.rotate(deltaAngle);
    ctx.translate(-centerX, -centerY);

    ctx.fillStyle = '#ff0000';
    ctx.beginPath();
    ctx.moveTo(100, 50);
    ctx.lineTo(50, 150);
    ctx.lineTo(150, 150);
    ctx.closePath();
    ctx.fill();

    ctx.strokeStyle = '#0000ff';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(100, 50);
    ctx.lineTo(50, 150);
    ctx.lineTo(150, 150);
    ctx.closePath();
    ctx.stroke();

    ctx.fillStyle = '#0000ff';
    ctx.beginPath();
    ctx.arc(100, 50, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(50, 150, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(150, 150, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(100, 150, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(75, 100, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(125, 100, radius, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  } else if (isScaling) {
    const scaleX = Math.sqrt(Math.pow(x - centerX, 2) + Math.pow(y - centerY, 2)) / Math.sqrt(Math.pow(startScaleX, 2) + Math.pow(startScaleY, 2));
    const scaleY = scaleX;

    ctx.clearRect(0, 0, canvas.width, canvas.height);

    ctx.save();
    ctx.translate(centerX, centerY);
    ctx.scale(scaleX, scaleY);
    ctx.translate(-centerX, -centerY);

    ctx.fillStyle = '#ff0000';
    ctx.beginPath();
    ctx.moveTo(100, 50);
    ctx.lineTo(50, 150);
    ctx.lineTo(150, 150);
    ctx.closePath();
    ctx.fill();

    ctx.strokeStyle = '#0000ff';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(100, 50);
    ctx.lineTo(50, 150);
    ctx.lineTo(150, 150);
    ctx.closePath();
    ctx.stroke();

    ctx.fillStyle = '#0000ff';
    ctx.beginPath();
    ctx.arc(100, 50, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(50, 150, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(150, 150, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(100, 150, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(75, 100, radius, 0, Math.PI * 2);
    ctx.fill();
    ctx.beginPath();
    ctx.arc(125, 100, radius, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  } else {
    const dx = x - centerX;
    const dy = y - centerY;
    const d = Math.sqrt(dx * dx + dy * dy);
    if (d <= radius) {
      canvas.style.cursor = 'pointer';
    } else {
      canvas.style.cursor = 'default';
    }
  }
}

function handleMouseUp(e) {
  isRotating = false;
  isScaling = false;
}

function isPointInCircle(x, y, cx, cy, r) {
  const dx = x - cx;
  const dy = y - cy;
  const d = Math.sqrt(dx * dx + dy * dy);
  return d <= r;
}

在示例2中,我们实现了一个三角形的旋转和缩放操作。用户可以拖动三角形中的任意一个点进行缩放,也

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:前端canvas中物体边框和控制点的实现示例 - Python技术站

(0)
上一篇 2023年6月10日
下一篇 2023年6月10日

相关文章

  • 简单总结CSS3中视窗单位Viewport的常见用法

    以下是关于CSS3中视窗单位Viewport的常见用法的详细攻略。 什么是Viewport Viewport是指浏览器窗口显示网页的区域,即视口区域。在CSS3中,为了满足不同设备和不同分辨率的网页需求,引入了视窗单位Viewport。 视窗单位Viewport的用法 Viewport单位有vw、vh、vmin和vmax四种,具体用法分别如下: vw、vh单…

    css 2023年6月10日
    00
  • 网页简历结构和语义信息 hResume微格式

    下面我将为你详细讲解网页简历结构和语义信息 hResume微格式的完整攻略。 什么是网页简历结构和语义信息? 网页简历结构和语义信息是指在编写个人简历网页时,合理地组织和展示简历信息,并通过添加适当的语义标签、元数据等信息,使得搜索引擎和其他机器能够更好地理解该简历,提高简历的可发现性和可读性。通俗地说,就是通过标准化地描述简历信息,来达到更好的展示和搜索结…

    css 2023年6月10日
    00
  • React Native基础入门之初步使用Flexbox布局

    React Native是基于React的框架,用于构建原生移动应用程序。在React Native中使用Flexbox布局非常常见,本攻略将针对初学者介绍React Native中的Flexbox。 什么是Flexbox布局 在Web开发中,CSS的Flexbox布局是一种灵活的布局方式,它可以轻松创建弹性布局。在React Native中,同样采用Fle…

    css 2023年6月9日
    00
  • css3模拟jq点击事件的实例代码

    CSS3 是 Web 技术中极为重要的部分,提供了丰富的样式效果以及交互特性。其中,模拟 jQuery 的点击事件是 CSS3 中常见的特性之一。 前置知识 在了解如何模拟点击事件之前,我们需要掌握以下内容: CSS3 选择器 CSS3 transition、animation 等动效属性 CSS3 伪元素 实现方式 CSS3 模拟点击事件的实现方式有多种,…

    css 2023年6月10日
    00
  • 使用CSS实现图片帧动画与曲线运动

    下面是使用CSS实现图片帧动画与曲线运动的完整攻略。 实现图片帧动画 步骤一:准备图片 首先需要准备好连续的若干张图片,这些图片可以是同一场景或者是不同场景,关键是这些图片的色彩和大小必须相同。 步骤二:定义CSS样式 接下来,我们需要定义CSS样式,来控制这些图片的位置和显示方式。首先设置带有“animation”的类的元素的宽度和高度,并且将其中的img…

    css 2023年6月11日
    00
  • 详解AngularJS实现表单验证

    AngularJS 是一个强大的JavaScript框架,被广泛用于构建 Web 应用程序,其中表单验证是重要功能之一。以下是实现表单验证的详细步骤: 步骤一:引入AngularJS 首先,在 HTML 中引入 AngularJS 库(CDN 方式或下载导入),这可以通过以下代码实现: <script src="https://cdnjs.c…

    css 2023年6月10日
    00
  • 提升WordPress 打开速度全面解决方案

    下面是关于提升WordPress打开速度的全面解决方案: 一、优化图片 处理过大的图片:网站的主要内容通常由文章和图片构成,其中图片往往是占用网页资源最多的元素,过大的图片会导致网页加载缓慢。如果你使用的是WordPress官方编辑器,在上传图片时可以选择“中等”或“缩略图”选项,这将限制图片的宽度和高度,同时压缩图片。另外,你也可以使用一些图片优化插件,如…

    css 2023年6月10日
    00
  • 使用CSS3实现按钮悬停闪烁动态特效代码

    下面是使用CSS3实现按钮悬停闪烁动态特效的完整攻略。 1. 原理简介 按钮悬停闪烁动态特效的实现主要涉及到 CSS3 中的两个关键特性:transition 和 animation。 transition 主要用于设置当按钮状态发生改变时的过渡效果,比如当鼠标悬停在按钮上时,按钮的背景颜色会发生改变。而 animation 主要用于实现按钮悬停时的闪烁效果…

    css 2023年6月10日
    00
合作推广
合作推广
分享本页
返回顶部