ReactNative for OpenHarmony项目鸿蒙化三方库:@react-native-ohos/image-editor
y: number;quality?: number;format?
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
📌 开发环境声明:本文基于 React Native 0.72.90 版本进行开发适配
🚀 一、开篇引言
图片编辑是移动应用中常见的需求,无论是头像裁剪、图片压缩还是格式转换,都离不开图片处理能力。@react-native-community/image-editor 是 React Native 社区中轻量级的图片编辑组件,提供图片裁剪、缩放、压缩等核心功能。本文将带你深入了解如何在 HarmonyOS 平台上集成和使用这个实用的图片处理组件。
1.1 你将学到什么?
- ✅ image-editor 的核心概念与工作原理
- ✅ HarmonyOS 平台的完整集成流程
- ✅ 图片裁剪与缩放处理
- ✅ API 的深度解析
- ✅ 实际应用场景的最佳实践
1.2 适用人群
- 正在进行 React Native 鸿蒙化迁移的开发者
- 需要实现图片裁剪功能的开发者
- 对跨平台图片处理开发感兴趣的技术爱好者
1.3 为什么选择 image-editor?
| 特点 | 说明 |
|---|---|
| 轻量级 | 专注于图片裁剪,无冗余功能 |
| 跨平台一致 | iOS、Android、HarmonyOS 表现一致 |
| 简单易用 | API 极简,一个方法完成裁剪 |
| 功能实用 | 支持裁剪、缩放、压缩、格式转换 |
| 性能优秀 | 原生实现,处理速度快 |
📦 二、库概览
2.1 基本信息
| 项目 | 内容 |
|---|---|
| 库名称 | @react-native-ohos/image-editor |
| 原库名称 | @react-native-community/image-editor |
| 版本信息 | 3.2.1 (RN 0.72) / 4.3.1 (RN 0.77) |
| 官方仓库 | https://github.com/callstack/react-native-image-editor |
| 鸿蒙仓库 | https://gitcode.com/openharmony-sig/rntpc_react-native-image-editor |
| 开源协议 | MIT |
2.2 版本兼容性
| 三方库版本 | 支持RN版本 | 是否支持Autolink |
|---|---|---|
| ~4.3.1 | 0.77 | No |
| ~3.2.1 | 0.72 | Yes |
| <=3.2.0-nc.0.1.3@deprecated | 0.72 | No |
2.3 核心能力矩阵
| 能力项 | 描述 | HarmonyOS 支持 |
|---|---|---|
| 图片裁剪 | cropImage 方法 | ✅ 完全支持 |
| 指定偏移量 | offset 参数 | ✅ 完全支持 |
| 指定尺寸 | size 参数 | ✅ 完全支持 |
| 缩放显示 | displaySize 参数 | ✅ 完全支持 |
| 质量压缩 | quality 参数 | ✅ 完全支持 |
| 格式转换 | format 参数 | ✅ 完全支持 |
2.4 技术架构图
2.5 典型应用场景
| 场景 | 描述 | 示例 |
|---|---|---|
| 头像裁剪 | 用户头像方形裁剪 | 👤 个人中心、账号设置 |
| 图片压缩 | 减小图片体积 | 📤 图片上传、节省流量 |
| 格式转换 | PNG/JPEG 格式转换 | 🖼️ 格式统一、兼容处理 |
| 缩略图生成 | 生成指定尺寸缩略图 | 📋 列表展示、预览图 |
🔧 三、环境准备
3.1 安装依赖

在项目根目录执行以下命令:
npm install @react-native-ohos/image-editor@3.2.1-rc.1
或使用 yarn:
yarn add @react-native-ohos/image-editor@3.2.1-rc.1
3.2 验证安装
安装完成后,检查 package.json 文件中是否包含以下依赖:
{
"dependencies": {
"@react-native-ohos/image-editor": "^3.2.1-rc.1"
}
}
⚙️ 四、原生配置
4.1 配置 oh-package.json5
打开 harmony/oh-package.json5,添加 overrides 配置(一定要根据自己项目的版本来配置):
{
"overrides": {
"@rnoh/react-native-openharmony": "^0.72.90"
}
}
4.2 配置 entry/oh-package.json5
打开 harmony/entry/oh-package.json5,添加依赖:
{
"dependencies": {
"@react-native-ohos/image-editor": "file:../../node_modules/@react-native-ohos/image-editor/harmony/image_editor.har"
}
}
4.3 配置 CMakeLists.txt
打开 harmony/entry/src/main/cpp/CMakeLists.txt,添加:
set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
# RNOH_BEGIN: manual_package_linking_1
add_subdirectory("${OH_MODULES}/@react-native-ohos/image-editor/src/main/cpp" ./image-editor)
# RNOH_END: manual_package_linking_1
# RNOH_BEGIN: manual_package_linking_2
target_link_libraries(rnoh_app PUBLIC rnoh_image_editor)
# RNOH_END: manual_package_linking_2
4.4 配置 PackageProvider.cpp
打开 harmony/entry/src/main/cpp/PackageProvider.cpp,添加:
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
#include "ReactNativeOhosReactNativeImageEditorPackage.h"
using namespace rnoh;
std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
return {
std::make_shared<RNOHGeneratedPackage>(ctx),
std::make_shared<ImageEditorPackage>(ctx),
};
}
4.5 配置 RNPackagesFactory.ts
打开 harmony/entry/src/main/ets/RNPackagesFactory.ts,添加:
import { ImageEditorPackage } from '@react-native-ohos/image-editor/ts';
export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
return [
new ImageEditorPackage(ctx)
];
}
4.6 同步依赖
在 DevEco Studio 中点击右上角的 sync 按钮,或在命令行执行:
cd harmony/entry
ohpm install
📖 五、API 详解
5.1 cropImage 方法
cropImage 是 image-editor 库的核心方法,用于裁剪指定 URI 的图片。该方法支持本地图片和网络图片,返回裁剪后图片的本地缓存路径。
方法签名:
static cropImage(uri: string, cropData: CropData): Promise<string>
参数说明:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| uri | string | 是 | 图片 URI,支持本地路径和网络 URL |
| cropData | CropData | 是 | 裁剪配置对象 |
返回值:
返回 Promise,成功时解析为裁剪后图片的本地缓存路径(string)。
使用示例:
import ImageEditor from "@react-native-community/image-editor";
const croppedImageURI = await ImageEditor.cropImage(
imageUri,
cropData
);
5.2 CropData 配置对象
CropData 是裁剪操作的核心配置对象,包含裁剪区域、输出尺寸、质量等参数。
offset - 裁剪偏移量
指定裁剪区域的左上角坐标,坐标系原点为原图左上角,x 轴向右为正,y 轴向下为正。
offset: {
x: number, // 水平偏移量(像素)
y: number // 垂直偏移量(像素)
}
示例:
offset: { x: 100, y: 100 }
这表示裁剪区域从原图坐标 (100, 100) 开始。
size - 裁剪尺寸
指定裁剪区域的宽度和高度,单位为像素。
size: {
width: number, // 裁剪宽度(像素)
height: number // 裁剪高度(像素)
}
示例:
size: { width: 300, height: 300 }
这表示裁剪一个 300x300 像素的正方形区域。
displaySize - 显示尺寸(可选)
指定裁剪后图片的最终显示尺寸。如果不设置,则保持裁剪区域的原始尺寸。
displaySize: {
width: number, // 输出宽度(像素)
height: number // 输出高度(像素)
}
示例:
displaySize: { width: 150, height: 150 }
这会将裁剪后的图片缩放到 150x150 像素。
resizeMode - 缩放模式(可选)
当设置了 displaySize 时,指定图片的缩放模式。
| 值 | 说明 |
|---|---|
| ‘cover’ | 保持宽高比缩放,填满整个区域,可能裁剪内容 |
| ‘contain’ | 保持宽高比缩放,完整显示内容,可能有留白 |
默认值: 'cover'
示例:
resizeMode: 'cover'
quality - 图片质量(可选)
指定输出图片的压缩质量,仅对 JPEG 格式有效。
| 值范围 | 说明 |
|---|---|
| 0.0 | 最低质量,最小文件体积 |
| 1.0 | 最高质量,最大文件体积 |
默认值: 0.9
示例:
quality: 0.8 // 80% 质量
format - 图片格式(可选)
指定输出图片的格式。
| 值 | 说明 |
|---|---|
| ‘jpeg’ | JPEG 格式,适合照片类图片 |
| ‘png’ | PNG 格式,适合透明背景图片 |
默认值: 根据输入图片格式自动判断,无法判断时使用 ‘jpeg’
示例:
format: 'jpeg'
5.3 完整 CropData 类型定义
interface CropData {
offset: {
x: number;
y: number;
};
size: {
width: number;
height: number;
};
displaySize?: {
width: number;
height: number;
};
resizeMode?: 'cover' | 'contain';
quality?: number;
format?: 'jpeg' | 'png';
}
🎯 六、属性详解
| 属性名 | 描述 | 类型 | 必填 | 默认值 | HarmonyOS 支持 |
|---|---|---|---|---|---|
| offset | 裁剪区域左上角坐标 | object | 是 | - | ✅ |
| size | 裁剪区域的尺寸 | object | 是 | - | ✅ |
| displaySize | 裁剪后图片的显示尺寸 | object | 否 | 裁剪尺寸 | ✅ |
| resizeMode | 缩放模式 | string | 否 | ‘cover’ | ✅ |
| quality | 压缩质量 (0.0-1.0) | number | 否 | 0.9 | ✅ |
| format | 输出格式 | string | 否 | 自动判断 | ✅ |
💡 七、使用示例
7.1 基础裁剪
最简单的裁剪方式,从图片左上角开始裁剪一个 200x200 的区域。
适用场景: 图片内容集中在左上角,需要快速裁剪固定尺寸。
import ImageEditor from "@react-native-community/image-editor";
const cropImage = async () => {
try {
const croppedImageURI = await ImageEditor.cropImage(
'https://example.com/image.jpg',
{
offset: { x: 0, y: 0 },
size: { width: 200, height: 200 },
}
);
console.log('裁剪后的图片路径:', croppedImageURI);
} catch (error) {
console.error('裁剪失败:', error);
}
};
代码解析:
offset: { x: 0, y: 0 }- 从原图左上角开始size: { width: 200, height: 200 }- 裁剪 200x200 像素区域- 返回的
croppedImageURI是裁剪后图片的本地缓存路径
7.2 带缩放和压缩的裁剪
在裁剪的同时进行缩放和压缩,适用于需要生成缩略图的场景。
适用场景: 生成头像缩略图、列表预览图等需要控制文件大小的场景。
const cropWithResize = async () => {
try {
const croppedImageURI = await ImageEditor.cropImage(
imageUri,
{
offset: { x: 50, y: 50 },
size: { width: 300, height: 300 },
displaySize: { width: 100, height: 100 },
quality: 0.8,
format: 'jpeg',
}
);
console.log('缩略图路径:', croppedImageURI);
} catch (error) {
console.error('处理失败:', error);
}
};
代码解析:
offset: { x: 50, y: 50 }- 从原图 (50, 50) 位置开始裁剪size: { width: 300, height: 300 }- 裁剪 300x300 像素区域displaySize: { width: 100, height: 100 }- 缩放到 100x100 像素输出quality: 0.8- 80% 质量,平衡文件大小和画质format: 'jpeg'- 输出为 JPEG 格式
7.3 正方形头像裁剪
自动计算裁剪区域,从图片中心裁剪出最大的正方形区域,常用于头像处理。
适用场景: 用户头像上传、个人资料图片处理。
const cropAvatar = async (imageUri, imageSize) => {
const size = Math.min(imageSize.width, imageSize.height);
const x = (imageSize.width - size) / 2;
const y = (imageSize.height - size) / 2;
try {
return await ImageEditor.cropImage(imageUri, {
offset: { x, y },
size: { width: size, height: size },
displaySize: { width: 200, height: 200 },
quality: 0.9,
format: 'jpeg',
});
} catch (error) {
console.error('头像裁剪失败:', error);
return null;
}
};
// 使用示例
const avatarUri = await cropAvatar(
'file:///path/to/image.jpg',
{ width: 1920, height: 1080 }
);
代码解析:
Math.min(imageSize.width, imageSize.height)- 取宽高中较小值作为正方形边长x = (imageSize.width - size) / 2- 计算水平居中偏移y = (imageSize.height - size) / 2- 计算垂直居中偏移- 最终从图片中心裁剪出最大正方形区域
7.4 结合图片选择器使用
实际项目中,通常需要先选择图片,获取尺寸后再进行裁剪。
适用场景: 完整的头像上传流程。
import { launchImageLibrary } from 'react-native-image-picker';
import ImageEditor from '@react-native-community/image-editor';
import { Image } from 'react-native';
const selectAndCropAvatar = async () => {
// 1. 选择图片
const result = await launchImageLibrary({
mediaType: 'photo',
quality: 1,
});
if (result.didCancel || result.errorCode) {
return null;
}
const imageUri = result.assets[0].uri;
// 2. 获取图片尺寸
Image.getSize(imageUri, async (width, height) => {
// 3. 计算正方形裁剪区域
const size = Math.min(width, height);
const x = (width - size) / 2;
const y = (height - size) / 2;
// 4. 执行裁剪
const croppedUri = await ImageEditor.cropImage(imageUri, {
offset: { x, y },
size: { width: size, height: size },
displaySize: { width: 200, height: 200 },
quality: 0.9,
format: 'jpeg',
});
return croppedUri;
});
};
代码解析:
- 使用
react-native-image-picker选择图片 - 使用
Image.getSize()获取图片原始尺寸 - 根据尺寸计算居中裁剪区域
- 执行裁剪并返回结果
❓ 八、常见问题
8.1 常见问题解答
Q1: 裁剪后图片路径是什么?
A: 裁剪后的图片存储在应用缓存目录中,返回的是缓存路径。使用完毕后建议手动清理。
Q2: 支持哪些图片格式?
A: 支持 JPEG 和 PNG 格式,可通过 format 参数指定输出格式。
Q3: 如何处理网络图片?
A: cropImage 方法会自动下载网络图片进行裁剪,下载失败会抛出异常。
Q4: 裁剪尺寸超出原图范围会怎样?
A: 会在调用时报错,建议在裁剪前检查 offset 和 size 是否超出原图尺寸。
Q5: 如何获取原图尺寸?
A: 可以使用 Image.getSize() 或 Image.getSizeWithHeaders() 方法获取图片尺寸。
8.2 最佳实践
- 裁剪前检查尺寸:确保裁剪区域不超出原图范围
- 合理设置质量:根据场景选择合适的压缩质量
- 及时清理缓存:裁剪后的图片在缓存中,使用后及时删除
- 错误处理:添加 try-catch 处理可能的异常
💻 九、完整示例代码
图片裁剪器示例

import React, { useState } from 'react';
import {
View,
Text,
StyleSheet,
SafeAreaView,
Image,
TouchableOpacity,
ScrollView,
Dimensions,
ActivityIndicator,
Alert,
} from 'react-native';
import ImageEditor from '@react-native-community/image-editor';
const { width: SCREEN_WIDTH } = Dimensions.get('window');
const DEMO_IMAGES = [
{
uri: 'https://octodex.github.com/images/OctoAsians_dex_Full.png',
width: 896,
height: 896,
},
{
uri: 'https://octodex.github.com/images/Professortocat_v2.png',
width: 896,
height: 896,
},
];
export default function App() {
const [currentImage, setCurrentImage] = useState(DEMO_IMAGES[0]);
const [croppedUri, setCroppedUri] = useState<string | null>(null);
const [isProcessing, setIsProcessing] = useState(false);
const [cropSize, setCropSize] = useState({ width: 300, height: 300 });
const handleCrop = async () => {
setIsProcessing(true);
try {
const result = await ImageEditor.cropImage(currentImage.uri, {
offset: { x: 100, y: 100 },
size: cropSize,
displaySize: { width: 200, height: 200 },
quality: 0.9,
format: 'jpeg',
});
setCroppedUri(result);
} catch (error) {
Alert.alert('裁剪失败', '请检查图片是否可访问');
} finally {
setIsProcessing(false);
}
};
const handleCropSquare = async () => {
setIsProcessing(true);
try {
const size = Math.min(currentImage.width, currentImage.height);
const result = await ImageEditor.cropImage(currentImage.uri, {
offset: { x: 0, y: 0 },
size: { width: size, height: size },
displaySize: { width: 200, height: 200 },
quality: 0.9,
format: 'jpeg',
});
setCroppedUri(result);
} catch (error) {
Alert.alert('裁剪失败', '请检查图片是否可访问');
} finally {
setIsProcessing(false);
}
};
const switchImage = () => {
const nextIndex = DEMO_IMAGES.indexOf(currentImage) === 0 ? 1 : 0;
setCurrentImage(DEMO_IMAGES[nextIndex]);
setCroppedUri(null);
};
const clearCropped = () => {
setCroppedUri(null);
};
return (
<SafeAreaView style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>图片裁剪器</Text>
</View>
<ScrollView style={styles.content} contentContainerStyle={styles.scrollContent}>
<View style={styles.section}>
<Text style={styles.sectionTitle}>原图</Text>
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
<Image
source={{ uri: currentImage.uri }}
style={styles.originalImage}
resizeMode="contain"
/>
</ScrollView>
</View>
<View style={styles.buttonGroup}>
<TouchableOpacity
style={[styles.button, styles.primaryButton]}
onPress={handleCrop}
disabled={isProcessing}
>
{isProcessing ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.buttonText}>裁剪 300x300</Text>
)}
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.secondaryButton]}
onPress={handleCropSquare}
disabled={isProcessing}
>
<Text style={styles.secondaryButtonText}>正方形裁剪</Text>
</TouchableOpacity>
<View style={styles.rowButtons}>
<TouchableOpacity
style={[styles.button, styles.outlineButton]}
onPress={switchImage}
>
<Text style={styles.outlineButtonText}>切换图片</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.outlineButton]}
onPress={clearCropped}
>
<Text style={styles.outlineButtonText}>清除结果</Text>
</TouchableOpacity>
</View>
</View>
{croppedUri && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>裁剪结果</Text>
<View style={styles.resultContainer}>
<Image
source={{ uri: croppedUri }}
style={styles.croppedImage}
resizeMode="contain"
/>
<Text style={styles.resultPath} numberOfLines={2}>
{croppedUri}
</Text>
</View>
</View>
)}
</ScrollView>
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#1a1a2e',
},
header: {
padding: 20,
backgroundColor: '#16213e',
},
title: {
fontSize: 24,
fontWeight: 'bold',
color: '#fff',
textAlign: 'center',
},
content: {
flex: 1,
},
scrollContent: {
padding: 16,
},
section: {
marginBottom: 24,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#fff',
marginBottom: 12,
},
originalImage: {
width: 300,
height: 300,
borderRadius: 12,
backgroundColor: '#16213e',
},
buttonGroup: {
gap: 12,
marginBottom: 24,
},
button: {
paddingVertical: 14,
borderRadius: 12,
alignItems: 'center',
},
primaryButton: {
backgroundColor: '#00d4ff',
},
secondaryButton: {
backgroundColor: '#16213e',
borderWidth: 2,
borderColor: '#00d4ff',
},
outlineButton: {
flex: 1,
backgroundColor: 'transparent',
borderWidth: 1,
borderColor: '#444',
paddingVertical: 12,
},
rowButtons: {
flexDirection: 'row',
gap: 12,
},
buttonText: {
fontSize: 16,
color: '#1a1a2e',
fontWeight: '600',
},
secondaryButtonText: {
fontSize: 16,
color: '#00d4ff',
fontWeight: '600',
},
outlineButtonText: {
fontSize: 14,
color: '#888',
},
resultContainer: {
alignItems: 'center',
backgroundColor: '#16213e',
borderRadius: 16,
padding: 20,
},
croppedImage: {
width: 200,
height: 200,
borderRadius: 8,
marginBottom: 16,
},
resultPath: {
fontSize: 12,
color: '#666',
textAlign: 'center',
},
});
🔗 十、相关资源
更多推荐


所有评论(0)