kp-image-crop
v0.1.0
Published
零依赖的原生图片裁剪组件:指定比例 / 任意比例两种模式,按原图像素无损导出
Maintainers
Readme
KPImageCrop
零依赖的原生图片裁剪组件。只做裁剪,两种模式:指定比例 与 任意比例。
- 不依赖 Vue / React,纯 TypeScript + DOM,任何项目都能用
- 组件自己只渲染两样东西:图片显示面 和 裁剪框。文件选择按钮、确认/取消、上传逻辑都由宿主项目自己的 UI 承担,组件只提供函数调用
- 裁剪结果按裁剪区在原图上的真实像素导出,显示缩放不影响输出分辨率
安装
npm i kp-image-crop包名不能含大写,因此 npm 包名是 kp-image-crop,类名仍为 KPImageCrop。
快速开始
import { KPImageCrop } from 'kp-image-crop';
// 宿主的文件选择容器由项目自己提供,组件只接收选出来的 File
const crop = new KPImageCrop({
container: document.querySelector('#crop-host'),
ratio: 'free', // 任意比例;改成 1 / 16 / 9 / 4:3 写法见下
});
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
if (file) await crop.load(file);
});
// 宿主的「确定」按钮
confirmBtn.addEventListener('click', async () => {
const { blob, width, height } = await crop.getCropBlob();
await upload(blob);
console.log(width, height);
});container 也可传选择器字符串。样式默认自动注入一次,无需额外 import 'kp-image-crop/style.css'。
两种模式
crop.setRatio('free'); // 任意比例:8 个把手各走各的
crop.setRatio(1); // 指定比例:正方形
crop.setRatio(16 / 9);
crop.setRatio([4, 3]); // 等价于 4/3
crop.getRatio(); // 读回当前设置指定比例时,把手拖动会保持对侧边/对角为锚点,另一轴按比例联动;裁剪框永远不会超出图片边界;装不下时等比缩框而不是破坏比例。切换比例会保持框的中心,取该中心下能容纳的最大面积。
API
| 方法 | 说明 |
| --- | --- |
| load(src, opts?) | 载入 File/Blob/URL/dataURL,返回原图 {width,height}(已按 EXIF 转正)。重复调用会释放上一张 |
| setRatio(ratio) / getRatio() | 切换 1、[4,3]、16/9、'free'、null |
| getRect() | 当前裁剪区,原图自然像素、浮点;未载入时为 null |
| setRect(rect) | 回填裁剪区,可只给部分字段;越界会被夹回图内 |
| fitToImage() | 裁剪框撑满图片(固定比例时为该比例下的最大内接框) |
| getNaturalSize() | 原图像素尺寸 |
| getDisplayScale() | 显示面相对原图的缩放系数,宿主要叠加自定义标记时用 |
| getCropBlob(opts?) | 裁剪为 Blob,返回 {blob,width,height,capped,rect} |
| getCropFile(name, opts?) | 同上但返回 File;name 不带扩展名时按输出 MIME 自动补 |
| recompute() | 容器宽度变化、或从 display:none 变为可见后重新计算显示缩放 |
| on(event, cb) | 'change'(裁剪框变化,回传 rect)与 'load';返回取消监听的函数 |
| destroy() | 摘除监听、释放位图与 objectURL、移除自身 DOM |
导出选项
await crop.getCropBlob({
type: 'image/jpeg', // 默认跟随原图格式
quality: 0.95, // JPEG/WebP 编码质量
width: 512, // 倍图输出宽度;只给一个维度时另一维按裁剪区比例推导
height: 512,
allowUpscale: false, // 默认不允许放大,见下
});无头裁剪
已经知道矩形(自己画选区、或复用上次位置)时可以完全绕开组件 UI:
import { cropImage } from 'kp-image-crop';
const { blob } = await cropImage(file, { x: 100, y: 50, width: 640, height: 320 });关于「不影响画质」
裁剪框只是取景建议,导出时重新按原图采样:
- 几何状态(
x/y/width/height)一直保存在原图自然像素空间,DOM 位置每帧由scale派生;导出不读 DOM 尺寸 - 因此图片显示得多小都一样:把 4000px 的图缩到 300px 宽显示,框出 640px 区域,输出的仍是 640×N 的原始像素,1:1 拷贝、零重采样
- 不指定
width/height就是无损路径;PNG → 裁剪 → PNG像素完全一致
需要如实说明的三点边界:
- JPEG 源无法做到字节无损。浏览器只能把画布重新编码,重编码即有损。默认
quality: 0.95并跟随源格式;要严格无损像素请显式type: 'image/png'(体积会明显变大)。 - 倍图不会造出细节。指定的输出宽高超过裁剪区原生像素时默认封顶到原生分辨率,返回结果里
capped: true;传allowUpscale: true才会插值放大。 - 超大画布会主动报错。Safari/iOS 的画布面积约 1670 万像素上限,超限时浏览器会静默产出空白图,组件改为抛出
CanvasTooLargeError(带suggestedWidth/suggestedHeight),宁可失败也不静默降质。
主题
覆盖 CSS 自定义属性即可,无需 !important:
#crop-host .kpic-root {
--kpic-handle-color: #fff;
--kpic-handle-size: 14px;
--kpic-border-color: rgba(255, 255, 255, 0.9);
--kpic-mask-opacity: 0.55; /* 框外压暗 */
--kpic-grid-color: rgba(255, 255, 255, 0.35); /* 三分线 */
}DOM 结构类名前缀 kpic-:.kpic-root、.kpic-image、.kpic-box、.kpic-handle[data-dir]。
已知边界
- 不做缩放、平移、旋转、形状蒙版——这些不在本组件范围内
- 容器初始宽度必须大于 0。若组件挂在
display:none的弹窗里,显示后要调一次recompute(),或传autoResize: true交给ResizeObserver - EXIF 方向由
createImageBitmap(..., {imageOrientation:'from-image'})处理;个别老浏览器识别错时可传load(src, { orientation: 6 })手工纠正 - 只支持浏览器端,无 SSR 用途;
destroy()后实例不可复用
本地开发
npm run demo # 起 demo 页(文件选择按钮在组件外部,演示宿主 UI 的分工)
npm test # 几何纯函数单测(含随机拖拽 fuzz)
npm run typecheck
npm run build
npm run check # 无头 Chrome 真实跑裁剪管线 + 合成指针拖拽
npm run check:dist # 同一套断言改跑打包产物 dist 而不是 srccheck 需要 npm run demo 已在跑。脚本通过 CDP awaitPromise 等待异步链路完成——--dump-dom 配 --virtual-time-budget 会在 toBlob/createImageBitmap 跑完前就截断,只能拿到前几项结果。
