”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 创建一个 React Hook 以任意角度旋转图像

创建一个 React Hook 以任意角度旋转图像

发布于2024-11-08
浏览:106

Creating a React Hook for Rotating Images at Any Angle

在Web开发中,您可能需要旋转图像,这在CSS中很容易做到。像这样的简单代码变换:rotate(90deg);。但是如果我们想用 JS 来做呢?

TLDR

将图像绘制到浏览器环境中的画布上并旋转它。但在此之前,我们需要做一些数学运算来保持原始图像的长宽比。

假设我们已经加载了图像,计算旋转后的图像可以如下完成:

const { PI, sin, cos, abs } = Math;
const angle = (degree * PI) / 180;
const sinAngle = sin(angle);
const cosAngle = cos(angle);

const rotatedWidth = abs(imageWidth * cosAngle)   abs(imageHeight * sinAngle);

const rotatedHeight = abs(imageWidth * sinAngle)   abs(imageHeight * cosAngle);

接下来,我们使用一些画布 API 来进行实际的旋转:

const canvas = document.createElement('canvas');

const { width: canvasWidth, height: canvasHeight } = canvas;
const canvasCtx2D = canvas.getContext('2d');

canvasCtx2D.clearRect(0, 0, canvasWidth, canvasHeight);
canvasCtx2D.translate(canvasWidth / 2, canvasHeight / 2);
canvasCtx2D.rotate(angle);

canvasCtx2D.drawImage(
  image,
  -imageWidth / 2,
  -imageHeight / 2,
  imageWidth,
  imageHeight,
);

return canvas.toDataURL('image/png');

包起来

核心代码到位后,我们可以进行一些优化并编写专用的 React hook 来使用它:

import { useEffect, useRef, useState } from 'react';

type RotatedImage = {
  src: string;
  width: number;
  height: number;
} | null;

let canvas: HTMLCanvasElement | null = null;
let canvasCtx2D: CanvasRenderingContext2D | null = null;

const getRotatedImage = (
  image: HTMLImageElement | null,
  rotation: number,
): RotatedImage => {
  canvas ??= document.createElement('canvas');
  canvasCtx2D ??= canvas.getContext('2d');

  if (!image || !canvasCtx2D) return null;

  const { width: imageWidth, height: imageHeight, currentSrc } = image;
  const degree = rotation % 360;
  if (!degree) {
    return {
      src: currentSrc,
      width: imageWidth,
      height: imageHeight,
    };
  }

  const { PI, sin, cos, abs } = Math;
  const angle = (degree * PI) / 180;
  const sinAngle = sin(angle);
  const cosAngle = cos(angle);

  canvas.width = abs(imageWidth * cosAngle)   abs(imageHeight * sinAngle);
  canvas.height = abs(imageWidth * sinAngle)   abs(imageHeight * cosAngle);

  // The width and height of the canvas will be automatically rounded.
  const { width: canvasWidth, height: canvasHeight } = canvas;

  canvasCtx2D.clearRect(0, 0, canvasWidth, canvasHeight);
  canvasCtx2D.translate(canvasWidth / 2, canvasHeight / 2);
  canvasCtx2D.rotate(angle);

  canvasCtx2D.drawImage(
    image,
    -imageWidth / 2,
    -imageHeight / 2,
    imageWidth,
    imageHeight,
  );

  const src = canvas.toDataURL('image/png');
  canvas.width = 0;
  canvas.height = 0;

  return {
    src,
    width: canvasWidth,
    height: canvasHeight,
  };
};

const useRotateImage = (imageSrc: string, rotation?: number): RotatedImage => {
  const imageEle = useRef(null);
  const [rotatedImage, setRotatedImage] = useState(null);

  useEffect(() => {
    if (typeof rotation === 'number') {
      let currImage = imageEle.current;

      if (currImage?.currentSrc !== imageSrc) {
        currImage = new Image();
        imageEle.current = currImage;

        currImage.src = imageSrc;
      }

      currImage.decode().then(
        () => setRotatedImage(getRotatedImage(currImage, rotation)),
        () => setRotatedImage(null),
      );
    }
  }, [imageSrc, rotation]);

  return rotatedImage;
};

export default useRotateImage;

这里我复用了同一个canvas元素,以减少重复创建。其次,需要注意的是,我在每次旋转后将其宽度和高度设置为0,以减少内存使用。对了,我还做了清理画布的操作。这是因为在HTML规范中当你修改画布的宽度和高度时(无论是否与之前相同)都会清除画布,这与canvasCtx2D.clearRect(0, 0, canvasWidth, canvasHeight)相同,这现代浏览器支持。

在useRotateImage中,我保留对图像元素的引用,并在image.decode()之后设置旋转图像状态,该状态在图像数据准备好后解析。

以下是一个在线用例:


如果您觉得这有帮助,请考虑 订阅我的时事通讯 以获取更多有关 Web 开发的有用文章和工具。感谢您的阅读!

版本声明 本文转载于:https://dev.to/zacharylee/creating-a-react-hook-for-rotating-images-at-any-angle-4nnb?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 在 JavaScript 中使用 setTimeout 时如何保留“this”引用?
    在 JavaScript 中使用 setTimeout 时如何保留“this”引用?
    setTimeout 和 JavaScript 中难以捉摸的“this”使用 setTimeout 函数时,开发者经常会遇到后续调用方法丢失的问题他们的预期上下文,导致看似未定义的方法。这通常是由于丢失“this”引用引起的。问题:考虑以下代码:test.prototype.method = fun...
    编程 发布于2024-12-22
  • 尽管代码有效,为什么 POST 请求无法捕获 PHP 中的输入?
    尽管代码有效,为什么 POST 请求无法捕获 PHP 中的输入?
    解决 PHP 中的 POST 请求故障在提供的代码片段中:action=''而不是:action="<?php echo $_SERVER['PHP_SELF'];?>";?>"检查 $_POST数组:表单提交后使用 var_dump 检查 $_POST 数...
    编程 发布于2024-12-22
  • Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta 中的列偏移发生了什么?
    Bootstrap 4 Beta:列偏移的删除和恢复Bootstrap 4 在其 Beta 1 版本中引入了重大更改柱子偏移了。然而,随着 Beta 2 的后续发布,这些变化已经逆转。从 offset-md-* 到 ml-auto在 Bootstrap 4 Beta 1 中, offset-md-*...
    编程 发布于2024-12-22
  • 为什么使用 SwingUtilities.invokeLater?
    为什么使用 SwingUtilities.invokeLater?
    SwingUtilities.invokeLater:仔细观察在 Swing 编程领域,SwingUtilities.invokeLater 起着至关重要的作用。这个方法到底是做什么的,它与在主线程中直接执行代码有何不同?SwingUtilities.invokeLater,顾名思义,将给定 Run...
    编程 发布于2024-12-22
  • 插入数据时如何修复“常规错误:2006 MySQL 服务器已消失”?
    插入数据时如何修复“常规错误:2006 MySQL 服务器已消失”?
    插入记录时如何解决“一般错误:2006 MySQL 服务器已消失”介绍:将数据插入 MySQL 数据库有时会导致错误“一般错误:2006 MySQL 服务器已消失”。当与服务器的连接丢失时会出现此错误,通常是由于 MySQL 配置中的两个变量之一所致。解决方案:解决此错误的关键是调整wait_tim...
    编程 发布于2024-12-22
  • 如何在 Spring 中流式传输大型 MySQL 结果集以避免 OutOfMemoryError?
    如何在 Spring 中流式传输大型 MySQL 结果集以避免 OutOfMemoryError?
    使用 MySQL 流式处理大型结果集在 Spring 应用程序中处理大量 MySQL 表时,当驱动程序试图执行以下操作时,可能会出现 OutOfMemoryException:将整个表加载到内存中。设置statement.setFetchSize(Integer.MIN_VALUE);可能还不够,因...
    编程 发布于2024-12-22
  • 在 SQL 中更新同一个表后,如何安全地更新触发器内的表?
    在 SQL 中更新同一个表后,如何安全地更新触发器内的表?
    更新同一个表后更新触发器中的表在 SQL 中,更新触发器后更新表同一张表提出了一个潜在的问题。这是因为该表已针对更新操作锁定,并且尝试在作为同一事务的一部分执行的触发器内访问该表可能会导致冲突。要规避此限制,您可以更新通过使用 BEFORE 选项而不是 AFTER 来影响触发器中的列。这允许您在原始...
    编程 发布于2024-12-22
  • 使用PHP的DOMDocument提取节点时如何保留HTML标签?
    使用PHP的DOMDocument提取节点时如何保留HTML标签?
    使用 DOMDocument 提取 HTML 节点的问题简介DOMDocument 是一个 PHP 类,提供了一种便捷的解析方法以及操作 HTML 文档。然而,当尝试在提取内容时保留 HTML 标签时,用户可能会遇到困难。本文深入探讨了 DOM 的基本概念,并提出了应对这一挑战的解决方案。理解 DO...
    编程 发布于2024-12-22
  • 如何在 Python 类型提示中指定函数类型?
    如何在 Python 类型提示中指定函数类型?
    在类型提示中指定函数类型在Python中,类型提示用于提供有关变量和函数参数的预期类型的​​可选元数据。然而,将变量的类型提示指定为函数类型似乎不清楚。解决方案尽管相关中缺少“typing.Function” PEP 483,您可以使用以下命令将变量的类型提示指定为函数类型"typing....
    编程 发布于2024-12-22
  • 如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    如何修复 macOS 上 Django 中的“配置不正确:加载 MySQLdb 模块时出错”?
    MySQL配置不正确:相对路径的问题在Django中运行python manage.py runserver时,可能会遇到以下错误:ImproperlyConfigured: Error loading MySQLdb module: dlopen(/Library/Python/2.7/site-...
    编程 发布于2024-12-22
  • 为什么我在发送 URL 编码的 POST 请求时收到 400 BAD REQUEST?
    为什么我在发送 URL 编码的 POST 请求时收到 400 BAD REQUEST?
    带有 URL 编码负载的 POST 请求当构造带有 URL 编码数据的 POST 请求时,经常会遇到 400 BAD REQUEST 响应,表明服务器无法理解负载。当有效负载提供不正确时,通常会出现此问题。标准库的 http.NewRequest(...) 方法期望将有效负载作为第三个参数提供,该参...
    编程 发布于2024-12-22
  • HTML 格式标签
    HTML 格式标签
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    编程 发布于2024-12-22
  • 为什么我不能直接从定义类中调用内联友元函数?
    为什么我不能直接从定义类中调用内联友元函数?
    内联友元函数的可见性范围尽管在类中定义了内联友元函数,但它们的实际范围超出了类的边界。 C 标准规定,内联友元函数的名称引用在最近的封闭命名空间范围中声明的函数。但是,在该命名空间内提供匹配声明之前,无法通过非限定或限定查找访问此声明的函数。范围。因此,从类本身、封闭作用域或成员函数直接调用友元函数...
    编程 发布于2024-12-22
  • ## [].forEach.call() 如何转换 JavaScript 类数组结构?
    ## [].forEach.call() 如何转换 JavaScript 类数组结构?
    JavaScript 中的数组转换:了解 [].forEach.call()使用 JavaScript 时,您可能会遇到使用以下代码片段[].forEach.call() 迭代元素列表。该技术允许使用数组函数扩展类似数组的结构,例如 NodeList。空数组 [] 用于访问 forEach 函数,该...
    编程 发布于2024-12-22
  • 如何使用 CSS 创建具有固定标题的可滚动表格?
    如何使用 CSS 创建具有固定标题的可滚动表格?
    如何使用 CSS 和固定标题制作可滚动表格在 Web 开发中,通常需要创建包含大量数据的表格需要滚动。然而,在滚动表格主体的同时保持固定的标题可能具有挑战性。以下是实现此效果的方法:HTML 结构首先,我们必须确保 HTML 结构正确。我们有一个带滚动条的外部 div,一个包含表格的内部 div,表...
    编程 发布于2024-12-22

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3