canvas + ts 实现将图片一分为二的功能,并打包发布至 npm

前言

最近公司的后台项目中,遇到个需求:如果客户上传的是 A3 样式的图片,前端需将该图片从中间切割,分为左右对等的两半,即 2 张 A4 样式的图片后,再传给服务器。一开始我去网上找了一些截图的库,比如 cropperjs 来实现该功能,后来学习了截图背后的原理,发现针对公司的具体需求,根本用不着安装第三方库,自己写一个纯函数即可实现,本文就来说说具体过程。

实现图片的一分为二

根据图片尺寸生成 canvas 画布

函数接收的参数为 File 类型的图片文件 imgFile,它可以通过 <input type="file"> 或是第三方 UI 库的上传组件获取。拿到了 imgFile 后,就可以传给 URL.createObjectURL() 获取图片的 URL 的字符串 imgObjUrl,然后生成图片 img

typescript 复制代码
// 获取图片文件名、类型备用
const imgName = imgFile.name
const imgType = imgFile.type
imgObjUrl = URL.createObjectURL(imgFile)
img = new Image()
img.src = imgObjUrl

注意,通过 URL.createObjectURL() 创建的 URL 的生命周期会与其创建时所在窗口的 document 绑定在一起,所以为了更好的性能和内存使用状况,需要在合适的地方调用 URL.revokeObjectURL() 释放 URL。

等图片加载完成 ,就可以创建 canvas 元素,让它的宽高属性值等于图片的固有的宽高。注意,设置 canvas 宽高的时候不能仅通过 canvas.style.width = img.naturalWidth + 'px'canvas.style.height = img.naturalHeight + 'px',这样设置的是样式尺寸,在不同的 devicePixelRatio 下会表现不一致:

typescript 复制代码
img.onload = () => {
  canvas = document.createElement('canvas')
  ctx = canvas.getContext('2d', { willReadFrequently: true })
  canvas.width = width = img.naturalWidth
  canvas.height = height = img.naturalHeight
}

因为之后会多次使用到 ctx.getImageData() 方法,所以给 canvas.getContext() 传入第二个参数 { willReadFrequently: true }, 迫使浏览器使用 2D canvas 并节省内存(而不是硬件加速)。否则可能会遇到如下警告:

绘制完整图片到 canvas 画布

有了图片,有了 canvas 画布,就可以将图片绘制到画布上去了。绘制前调用 ctx.clearRect() 清空画布,其中 widthheight 等于图片的固有宽高,也等于画布的宽高:

typescript 复制代码
ctx.clearRect(0, 0, width, height)
ctx.drawImage(img, 0, 0, width, height)

获取 canvas 画布半边的像素数据

接着就可以通过 ctx.getImageData() 方法获取 canvas 画布的左右两边的隐含像素数据了,传入的参数分别代表要提取数据的矩形区域的左上角 x,y 坐标和宽高。比如我们要提取左半边的数据:

typescript 复制代码
const imgData = ctx.getImageData(0, 0, width / 2, height)

如果上传一张纯红色的图片,打印 imgData 获取到的结果如下,data 属性中就是各个像素点的颜色信息 ------ 255, 0, 0 即为纯红色 :

如果你想对图片进行置灰、反相之类的颜色处理,可以通过改变 data 中的数据实现。

根据像素数据生成新的 canvas

创建一个新的 canvas 画布,该画布的宽度为完整图片的一半,高度保持一致,然后把得到的半边图片的像素数据通过 ctx2.putImageData() 从新画布的 (0, 0) 点开始绘制到新画布上:

typescript 复制代码
const screenshotCanvas = document.createElement('canvas')
const screenshotCtx = screenshotCanvas.getContext('2d')
screenshotCanvas.width = width / 2
screenshotCanvas.height = height
screenshotCtx.putImageData(imgData, 0, 0)

canvas 生成 File

canvas 画布生成 File 对象的过程是先由 canvas 的 toBlob() 方法让画布上的图片生成 Blob 类型的对象 blob,再把 blob 传给 new File() 生成 File 对象,第 2、3 个参数分别指定文件的名称和类型:

typescript 复制代码
screenshotCanvas.toBlob((blob) => {
  if (!blob) return
  const file = new File([blob], `${position}-${imgName}`, { type: imgType })
  files.push(file)
}, imgType)

打印查看 file 对象,可以看到它的原型对象 File 的原型对象,其实就是 Blob

现在,当用户上传 A3 样式的图片时,实际传给服务器的文件数据,就是切割图片后生成的文件对象数组 files 了。

打包发布到 npm

之前在 《npm 细节与原理探究,顺便发布个自己的包》里介绍过如何将一个用 js 编写的项目打包发布到 npm 的仓库,本项目中的代码是使用 ts 编写的,应该如何打包呢?其实就是多了个将 ts 文件编译为 js 文件的过程,其中涉及到以下 2 个文件的修改或创建:

package.json

在 package.json 中,项目的主入口文件 "main" 的值需要是个 js 文件,所以需要安装 typescript 以便使用 tsc 编译 ts 文件;

"types":值为 ts 类型声明文件,tsc 编译时生成;

"scripts":添加打包命令 build,执行时就会编译 tsconfig.json 中 include 指定的 ts 文件,prepublishOnly 则是在发布到 npm 仓库前执行的命令:

json 复制代码
{
  "name": "bisect-image-to-files",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "npx tsc",
    "prepublishOnly": "pnpm build"
  }
}

tsconfig.json

执行 tsc --init 可以生成 tsconfig.json:

json 复制代码
{
  "compilerOptions": {
    "target": "es2015", 
    "module": "ES2015",
    "rootDir": "./src",
    "declaration": true, 
    "sourceMap": true, 
    "outDir": "./dist",
  },
  "include": ["src/**/*.ts"],
  "exclude": ["node_modules", "dist"]
}

compilerOptions 对象中:

  • target:指定编译生成的 js 文件的 ECMAScript 版本;
  • module:指定模块化方案;
  • rootDir:指定源文件的目录,也就是我们编写的 ts 文件的目录;
  • declaration:为 true 则会在编译时生成 .d.ts 文件;
  • sourceMap:为 true 则是在编译后生成 .map 文件,方便问题追踪;
  • outDir:指定编译后生成的文件的存放目录。

include:指定哪些目录下的 ts 文件需要编译;

exclude:指定哪些文件不用被编译。

其它步骤则和发布 js 编写的项目一样:登录 npm 仓库,然后通过 npm publish 发布即可。发布成功后就能在 npm 仓库中搜到本项目了:

效果演示

在使用了 element-plus 的框架的 vue3 后台项目中,安装我们发布到 npm 的包:

powershell 复制代码
pnpm add bisect-image-to-files

在需要上传图片的组件内就可以直接引入使用了:

vue 复制代码
<script lang="tsx" setup>
  import bisectImageToFiles from 'bisect-image-to-files'
  const [左半边图片文件, 右半边图片文件] = await bisectImageToFiles(图片文件)
</script>

我用 express 写了个服务器接收上传的图片,效果如下,在前端点击上传图片后,就能在服务器项目中的静态目录 uploads 中查看到左右两张图片了:

完整代码:github/gitee

相关推荐
weifexie24 分钟前
ruby可变参数
开发语言·前端·ruby
千野竹之卫25 分钟前
3D珠宝渲染用什么软件比较好?渲染100邀请码1a12
开发语言·前端·javascript·3d·3dsmax
sunbyte25 分钟前
初识 Three.js:开启你的 Web 3D 世界 ✨
前端·javascript·3d
半兽先生1 小时前
WebRtc 视频流卡顿黑屏解决方案
java·前端·webrtc
南星沐2 小时前
Spring Boot 常用依赖介绍
java·前端·spring boot
孙_华鹏2 小时前
手撸一个可以语音操作高德地图的AI智能体
前端·javascript·coze
zhangxingchao2 小时前
Jetpack Compose 动画
前端
@PHARAOH3 小时前
HOW - 缓存 React 自定义 hook 的所有返回值(包括函数)
前端·react.js·缓存
拉不动的猪3 小时前
设计模式之--------工厂模式
前端·javascript·架构
前端开发张小七3 小时前
16.Python递归详解:从原理到实战的完整指南
前端·python