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

相关推荐
passerby606140 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了1 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅1 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte3 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc