#HarmonyOS NEXT体验官# 如何用HarmonyOS Next制作Gif动画 原创
本文会详细介绍如何借用混合开发的方式使用HarmonyOS Next和gif.js制作GIF动画,下图是App的主界面。
下图是设置GIF属性:
下图是预览添加的静态图片:
下图是设置拉伸效果的窗口
下面详细描述app的实现过程:
首先看一下拉伸效果窗口的组件,代码如下:
代码详解与中文注释
这段代码实现了一个用于选择图片拉伸方式的用户界面。用户可以通过单选按钮选择不同的图片拉伸方式,并点击“确定”按钮将选择的拉伸方式应用。以下是对代码的逐行中文注释和实现原理的详细解释。
// 引入常量,用于设置面板和按钮的背景颜色,保证界面的一致性
import { PANEL_BACKGROUND_COLOR, PANEL_BUTTON_BACKGROUND_COLOR } from '../common/const';
// 引入与文档尺寸相关的常量,通常用于处理文档的尺寸设置
import { DocumentSize, DocumentSizes } from '../data/Documents';
// 引入 EntryAbility 模块,提供应用程序的全局路径信息
import EntryAbility from '../entryability/EntryAbility';
// 引入文件处理相关的工具函数,检查文档是否存在和创建文档目录
import { documentExists, createDocumentDir } from '../common/common';
// 使用 @Entry 装饰器标记这个组件为应用的入口组件,表示它会在应用启动时自动加载并显示
@Entry
// 使用 @Component 装饰器定义一个组件,名为 ScalePanel,包含了组件的逻辑和 UI 布局
@Component
export struct ScalePanel {
// 定义一个可选的回调函数,用于在用户选择了拉伸方式后,将选中的拉伸方式传递出去
scaleSelected?: (scale: string) => void;
// 从 EntryAbility 模块获取应用程序的根路径,通常用于处理文件路径等操作
rootPath: string = EntryAbility.rootPath;
// 使用 @State 定义多个状态变量,用于存储不同拉伸方式的选中状态
@State startInside: boolean = true; // 初始值设为 true,表示默认选中 'startInside'
@State fitXY: boolean = false; // 初始值设为 false,表示未选中 'fitXY'
@State centerCrop: boolean = false; // 初始值设为 false,表示未选中 'centerCrop'
@State centerInside: boolean = false;// 初始值设为 false,表示未选中 'centerInside'
@State formatName: string = ''; // 存储格式名称的状态变量,初始值为空字符串
// build() 方法是组件的核心方法,用于定义组件的 UI 布局和行为
build() {
// 使用 Column 组件创建一个垂直布局的容器,子组件将按照顺序从上到下排列
Column() {
// 创建一个 Row 组件,用于水平排列拉伸方式 'startInside' 的单选按钮和文本
Row() {
// 创建一个 Radio 单选按钮,值为 'startInside',属于 'radioGroup' 组
Radio({ value: 'startInside', group: 'radioGroup' })
.radioStyle({
checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
})
.checked(this.startInside) // 将单选按钮的选中状态与 startInside 变量绑定
.height(25) // 设置单选按钮的高度为 25
.width(25) // 设置单选按钮的宽度为 25
.onChange((isChecked: boolean) => { // 设置状态变化的回调函数
this.startInside = isChecked; // 当单选按钮状态变化时,更新 startInside 的值
});
// 创建一个文本组件,显示 'startInside',并设置字体大小和颜色
Text('startInside').fontSize(20).foregroundColor('#FFFFFF')
.onClick(() => {
this.startInside = !this.startInside; // 点击文本时,切换 startInside 的选中状态
});
}
// 创建一个 Row 组件,用于水平排列拉伸方式 'fitXY' 的单选按钮和文本
Row() {
// 创建一个 Radio 单选按钮,值为 'fitXY',属于 'radioGroup' 组
Radio({ value: 'fitXY', group: 'radioGroup' })
.radioStyle({
checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
})
.checked(this.fitXY) // 将单选按钮的选中状态与 fitXY 变量绑定
.height(25) // 设置单选按钮的高度为 25
.width(25) // 设置单选按钮的宽度为 25
.onChange((isChecked: boolean) => { // 设置状态变化的回调函数
this.fitXY = isChecked; // 当单选按钮状态变化时,更新 fitXY 的值
});
// 创建一个文本组件,显示 'fitXY',并设置字体大小和颜色
Text('fitXY').fontSize(20).foregroundColor('#FFFFFF')
.onClick(() => {
this.fitXY = !this.fitXY; // 点击文本时,切换 fitXY 的选中状态
});
}
// 创建一个 Row 组件,用于水平排列拉伸方式 'centerCrop' 的单选按钮和文本
Row() {
// 创建一个 Radio 单选按钮,值为 'centerCrop',属于 'radioGroup' 组
Radio({ value: 'centerCrop', group: 'radioGroup' })
.radioStyle({
checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
})
.checked(this.centerCrop) // 将单选按钮的选中状态与 centerCrop 变量绑定
.height(25) // 设置单选按钮的高度为 25
.width(25) // 设置单选按钮的宽度为 25
.onChange((isChecked: boolean) => { // 设置状态变化的回调函数
this.centerCrop = isChecked; // 当单选按钮状态变化时,更新 centerCrop 的值
});
// 创建一个文本组件,显示 'centerCrop',并设置字体大小和颜色
Text('centerCrop').fontSize(20).foregroundColor('#FFFFFF')
.onClick(() => {
this.centerCrop = !this.centerCrop; // 点击文本时,切换 centerCrop 的选中状态
});
}
// 创建一个 Row 组件,用于水平排列拉伸方式 'centerInside' 的单选按钮和文本
Row() {
// 创建一个 Radio 单选按钮,值为 'centerInside',属于 'radioGroup' 组
Radio({ value: 'centerInside', group: 'radioGroup' })
.radioStyle({
checkedBackgroundColor: Color.Pink // 设置选中状态的背景颜色为粉色
})
.checked(this.centerInside) // 将单选按钮的选中状态与 centerInside 变量绑定
.height(25) // 设置单选按钮的高度为 25
.width(25) // 设置单选按钮的宽度为 25
.onChange((isChecked: boolean) => { // 设置状态变化的回调函数
this.centerInside = isChecked; // 当单选按钮状态变化时,更新 centerInside 的值
});
// 创建一个文本组件,显示 'centerInside',并设置字体大小和颜色
Text('centerInside').fontSize(20).foregroundColor('#FFFFFF')
.onClick(() => {
this.centerInside = !this.centerInside; // 点击文本时,切换 centerInside 的选中状态
});
}
// 创建一个“确定”按钮,用于提交用户选择的拉伸方式
Button({ type: ButtonType.Normal }) {
// 在按钮内显示的文本内容为“确定”
Text('确定')
.fontSize(16) // 设置字体大小
.fontColor('#FFFFFF') // 设置字体颜色
.width('100%') // 设置文本的宽度为 100%
.height('100%') // 设置文本的高度为 100%
.textAlign(TextAlign.Center); // 设置文本居中对齐
}
.borderRadius(5) // 设置按钮的圆角半径,使按钮看起来更圆滑
.width('100%') // 设置按钮的宽度为 100%
.height(30) // 设置按钮的高度为 30
.margin({ bottom: 10, top: 10 }) // 设置按钮的上下边距
.onClick(() => {
// 设置按钮的点击事件,当用户点击“确定”按钮时,检查各个拉伸方式的选中状态
let scale: string = '';
if (this.startInside) {
scale = 'startInside'; // 如果 'startInside' 被选中,将其值赋给 scale
} else if (this.fitXY) {
scale = 'fitXY'; // 如果 'fitXY' 被选中,将其值赋给 scale
} else if (this.centerCrop) {
scale = 'centerCrop'; // 如果 'centerCrop' 被选中,将其值赋给 scale
} else if (this.centerInside) {
scale = 'centerInside'; // 如果 'centerInside' 被选中,将其值赋给 scale
}
// 如果 scaleSelected 回调函数存在,则调用该函数并传递选中的拉伸方式
if (this.scaleSelected) {
this.scaleSelected(scale);
}
});
}
.backgroundColor(PANEL_BACKGROUND_COLOR) // 设置背景颜色
.border({ width: 0, color: '#000000', radius: 10 }) // 设置边框属性
.width('100%') // 设置宽度为 100%,占据整个父容器的宽度
.height('100%') // 设置高度为 100%,占据整个父容器的高度
.padding({ top: 10, left: 10, right: 10 }) // 设置内边距
.alignItems(HorizontalAlign.Start); // 设置子组件的水平对齐方式
}
}
实现原理和步骤详解
-
组件初始化:
- 在组件定义时,引入了一些必要的常量、工具函数和模块,设置了状态变量以跟踪用户对不同图片拉伸方式的选择。
-
状态管理:
- 使用
@State
装饰器定义多个布尔类型的状态变量,如startInside
、fitXY
、centerCrop
和centerInside
,用于记录当前选中的拉伸方式。 - 这些状态变量初始值分别为
true
或false
,表示默认选中状态。
- 使用
-
UI 构建:
- 在
build()
方法中,使用 ArkTS 提供的 UI 组件(如Column
、Row
、Radio
和Button
)构建用户界面。 - 使用
Column
组件作为外层容器,内部包含多个Row
组件,每个Row
组件包含一个单选按钮 (Radio
) 和对应的文本 (Text
)。
- 在
-
单选按钮实现:
- 每个单选按钮绑定到相应的状态变量,并通过
onChange
事件处理函数动态更新选中状态。 - 通过
group
属性将所有单选按钮归为一组,确保在同一时间只能选中一个选项。
- 每个单选按钮绑定到相应的状态变量,并通过
-
确定按钮实现:
- 当用户点击“确定”按钮时,代码会检查当前选中的拉伸方式,并将其值存储在
scale
变量中。 - 如果定义了
scaleSelected
回调函数,则调用该函数并传递选中的拉伸方式。这个过程可以将用户选择的结果传递给外部处理逻辑。
- 当用户点击“确定”按钮时,代码会检查当前选中的拉伸方式,并将其值存储在
-
界面样式设置:
- 设置了整体界面的样式,包括背景颜色、边框属性、内边距和对齐方式,确保界面美观且易于使用。
下面再继续介绍设置窗口的实现,这个窗口是通过SettingsConfig组件实现的,下面是对这个组件的详细介绍。
代码详解与中文注释
这段代码实现了一个设置面板(SettingsPanel),用于用户配置一些图像处理的参数,如延迟时间、工作线程数、图像质量、图像宽度和高度、循环次数、是否防止抖动等。用户可以通过此面板进行参数设置,并点击“确定”或“取消”按钮提交或取消设置。以下是对代码的逐行中文注释和实现原理的详细解释。
// 引入常量,用于设置面板和按钮的背景颜色,保证界面的一致性
import { PANEL_BACKGROUND_COLOR, PANEL_BUTTON_BACKGROUND_COLOR } from '../common/const';
// 引入与文档尺寸相关的常量,通常用于处理文档的尺寸设置
import { DocumentSize, DocumentSizes } from '../data/Documents';
// 引入 EntryAbility 模块,提供应用程序的全局路径信息
import EntryAbility from '../entryability/EntryAbility';
// 引入文件处理相关的工具函数,检查文档是否存在,创建文档目录,以及获取反色颜色
import { documentExists, createDocumentDir, getInverseColor } from '../common/common';
// 定义一个设置配置类,包含多种配置项
export class SettingsConfig {
workers: number = 2; // 工作线程数
quality: number = 10; // 图像质量
width?: number; // 图像宽度(可选)
delay: number = 500; // 延迟时间
height?: number; // 图像高度(可选)
infiniteLoop: boolean = true; // 是否无限循环
repeat: number = 0; // 动画循环次数
dither: boolean = true; // 是否处理抖动
effects: string[] = []; // 图像效果列表
scaleType: string = 'startInside'; // 图像缩放类型
}
// 使用 @Component 装饰器定义一个组件,名为 SettingsPanel,包含了组件的逻辑和 UI 布局
@Component
export struct SettingsPanel {
// 定义两个可选的回调函数,分别用于设置确定和取消的处理逻辑
settingsOK?: (config: SettingsConfig) => void;
settingsCancel?: () => void;
// 使用 @State 定义一个状态变量,存储用户配置的设置项
@State config: SettingsConfig = new SettingsConfig();
// build() 方法是组件的核心方法,用于定义组件的 UI 布局和行为
build() {
// 使用 Column 组件创建一个垂直布局的容器,子组件将按照顺序从上到下排列
Column() {
// 创建一个文本组件,用于显示“延迟时间”标签
Text('延迟时间')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('100') // 设置宽度为 100
.height('30') // 设置高度为 30
.textAlign(TextAlign.Center) // 设置文本居中对齐
.margin({ bottom: 5, top: 10 }); // 设置上下边距
// 创建一个文本输入框,用于输入延迟时间
TextInput({ text: `${this.config.delay}`, placeholder: '请输入延迟时间' })
.fontSize(16) // 设置字体大小为 16
.width(200) // 设置宽度为 200
.height(40) // 设置高度为 40
.margin({ top: 10 }) // 设置上边距
.fontColor('#000000') // 设置字体颜色为黑色
.backgroundColor('#FFFFFF') // 设置背景颜色为白色
.type(InputType.Number) // 设置输入框的类型为数字
.onChange((value: string) => {
this.config.delay = parseInt(value); // 当输入框内容变化时,更新延迟时间
});
// 创建一个滑动条组件,用于选择工作线程数
Slider({
value: this.config.workers, // 绑定到状态变量 config.workers
min: 1, // 设置最小值为 1
max: 5, // 设置最大值为 5
step: 1, // 设置滑动步长为 1
style: SliderStyle.OutSet // 设置滑动条的样式
})
.margin({ left: 10, right: 10 }) // 设置左右边距
.onChange((value: number, mode: SliderChangeMode) => {
this.config.workers = Math.round(value); // 当滑动条变化时,更新工作线程数
});
// 创建一个 Row 组件,用于水平排列图像质量标签和当前数值
Row() {
// 创建一个文本组件,显示“图像质量”标签
Text('图像质量:')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('80') // 设置宽度为 80
.height('30') // 设置高度为 30
.textAlign(TextAlign.Center); // 设置文本居中对齐
// 创建一个文本组件,显示当前的图像质量数值
Text(`${this.config.quality}`)
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('50') // 设置宽度为 50
.height('30') // 设置高度为 30
.textAlign(TextAlign.Start); // 设置文本左对齐
}
.margin({ top: 10 }); // 设置上边距
// 创建一个滑动条组件,用于选择图像质量
Slider({
value: this.config.quality, // 绑定到状态变量 config.quality
min: 1, // 设置最小值为 1
max: 10, // 设置最大值为 10
step: 1, // 设置滑动步长为 1
style: SliderStyle.OutSet // 设置滑动条的样式
})
.margin({ left: 10, right: 10 }) // 设置左右边距
.onChange((value: number, mode: SliderChangeMode) => {
this.config.quality = Math.round(value); // 当滑动条变化时,更新图像质量
});
// 创建一个文本组件,用于显示“图像宽度”标签
Text('图像宽度')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('100') // 设置宽度为 100
.height('30') // 设置高度为 30
.textAlign(TextAlign.Center) // 设置文本居中对齐
.margin({ bottom: 5, top: 10 }); // 设置上下边距
// 创建一个文本输入框,用于输入图像宽度
TextInput({ placeholder: '请输入图像宽度' })
.fontSize(16) // 设置字体大小为 16
.width(200) // 设置宽度为 200
.height(40) // 设置高度为 40
.margin({ top: 10 }) // 设置上边距
.fontColor('#000000') // 设置字体颜色为黑色
.backgroundColor('#FFFFFF') // 设置背景颜色为白色
.type(InputType.Number) // 设置输入框的类型为数字
.onChange((value: string) => {
this.config.width = parseInt(value); // 当输入框内容变化时,更新图像宽度
});
// 创建一个文本组件,用于显示“图像高度”标签
Text('图像高度')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('100') // 设置宽度为 100
.height('30') // 设置高度为 30
.textAlign(TextAlign.Center) // 设置文本居中对齐
.margin({ bottom: 5, top: 10 }); // 设置上下边距
// 创建一个文本输入框,用于输入图像高度
TextInput({ placeholder: '请输入图像高度' })
.fontSize(16) // 设置字体大小为 16
.width(200) // 设置宽度为 200
.height(40) // 设置高度为 40
.margin({ top: 10 }) // 设置上边距
.fontColor('#000000') // 设置字体颜色为黑色
.backgroundColor('#FFFFFF') // 设置背景颜色为白色
.type(InputType.Number) // 设置输入框的类型为数字
.onChange((value: string) => {
this.config.height = parseInt(value); // 当输入框内容变化时,更新图像高度
});
// 创建一个 Flex 容器,用于布局无限循环的复选框和标签
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
// 创建一个复选框组件,用于选择是否无限循环
Checkbox({ name: '
infiniteLoop' })
.selectedColor(0x39a2db) // 设置选中颜色为蓝色
.select(this.config.infiniteLoop) // 绑定到状态变量 config.infiniteLoop
.shape(CheckBoxShape.ROUNDED_SQUARE) // 设置复选框形状为圆角矩形
.onChange((value: boolean) => {
this.config.infiniteLoop = value; // 当复选框状态变化时,更新无限循环的值
})
.width(25) // 设置复选框宽度为 25
.height(25); // 设置复选框高度为 25
// 创建一个文本组件,显示“无限循环”标签
Text('无限循环').fontSize(16).foregroundColor('#FFFFFF')
.onClick(() => {
this.config.infiniteLoop = !this.config.infiniteLoop; // 点击文本时,切换无限循环的状态
});
}
.margin({ top: 10, left: 10 }); // 设置上边距和左边距
// 创建一个文本组件,用于显示“循环次数”标签
Text('循环次数')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('200') // 设置宽度为 200
.height('30') // 设置高度为 30
.textAlign(TextAlign.Center) // 设置文本居中对齐
.margin({ bottom: 5, top: 10 }); // 设置上下边距
// 创建一个提示文本,解释循环次数的设置
Text('-1:不循环,0:无限循环')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('200') // 设置宽度为 200
.height('30') // 设置高度为 30
.textAlign(TextAlign.Center) // 设置文本居中对齐
.margin({ bottom: 5, top: 4 }); // 设置上下边距
// 创建一个文本输入框,用于输入动画的循环次数
TextInput({ text: `${this.config.repeat}`, placeholder: '请输入Gif动画循环次数' })
.fontSize(16) // 设置字体大小为 16
.width(200) // 设置宽度为 200
.height(40) // 设置高度为 40
.margin({ top: 10 }) // 设置上边距
.fontColor('#000000') // 设置字体颜色为黑色
.backgroundColor('#FFFFFF') // 设置背景颜色为白色
.onChange((value: string) => {
this.config.repeat = parseInt(value); // 当输入框内容变化时,更新循环次数
});
// 创建一个 Flex 容器,用于布局防止抖动的复选框和标签
Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) {
// 创建一个复选框组件,用于选择是否防止抖动
Checkbox({ name: 'dither' })
.selectedColor(0x39a2db) // 设置选中颜色为蓝色
.select(this.config.dither) // 绑定到状态变量 config.dither
.shape(CheckBoxShape.ROUNDED_SQUARE) // 设置复选框形状为圆角矩形
.onChange((value: boolean) => {
this.config.dither = value; // 当复选框状态变化时,更新防止抖动的值
})
.width(25) // 设置复选框宽度为 25
.height(25); // 设置复选框高度为 25
// 创建一个文本组件,显示“防止抖动”标签
Text('防止抖动').fontSize(16).foregroundColor('#FFFFFF')
.onClick(() => {
this.config.dither = !this.config.dither; // 点击文本时,切换防止抖动的状态
});
}
.margin({ top: 10, left: 10 }); // 设置上边距和左边距
// 创建一个 Row 容器,用于布局“确定”和“取消”按钮
Row() {
// 创建“确定”按钮
Button({ type: ButtonType.Normal }) {
Text('确定')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('100%') // 设置文本宽度为 100%
.height('30') // 设置文本高度为 30
.textAlign(TextAlign.Center); // 设置文本居中对齐
}
.borderRadius(5) // 设置按钮的圆角半径
.width(100) // 设置按钮宽度为 100
.height(30) // 设置按钮高度为 30
.margin({ right: 10 }) // 设置右边距
.onClick(() => {
if (this.settingsOK) {
this.settingsOK(this.config); // 当点击按钮时,调用 settingsOK 回调函数,并传递当前的配置
}
});
// 创建“取消”按钮
Button({ type: ButtonType.Normal }) {
Text('取消')
.fontSize(16) // 设置字体大小为 16
.fontColor('#FFFFFF') // 设置字体颜色为白色
.width('100%') // 设置文本宽度为 100%
.height('30') // 设置文本高度为 30
.textAlign(TextAlign.Center); // 设置文本居中对齐
}
.borderRadius(5) // 设置按钮的圆角半径
.width(100) // 设置按钮宽度为 100
.height(30) // 设置按钮高度为 30
.onClick(() => {
if (this.settingsCancel) {
this.settingsCancel(); // 当点击按钮时,调用 settingsCancel 回调函数
}
})
.margin({ left: 10 }); // 设置左边距
}
.margin({ top: 20, left: 10, right: 10 }); // 设置容器的上边距、左边距和右边距
}
.backgroundColor(PANEL_BACKGROUND_COLOR) // 设置背景颜色
.border({ width: 0, color: '#000000', radius: 10 }) // 设置边框属性
.width('100%') // 设置宽度为 100%,占据整个父容器的宽度
.height('100%'); // 设置高度为 100%,占据整个父容器的高度
}
}
代码实现原理和步骤详解
-
组件初始化:
- 通过引入必要的模块和工具函数,准备了 UI 布局所需的常量,并定义了配置类
SettingsConfig
,该类包含了多个图像处理相关的配置项。
- 通过引入必要的模块和工具函数,准备了 UI 布局所需的常量,并定义了配置类
-
状态管理:
- 使用
@State
装饰器定义了config
状态变量,存储用户设置的配置信息。config
是一个SettingsConfig
对象,包含了所有可配置的参数,如工作线程数、图像质量、图像宽度和高度等。
- 使用
-
UI 布局:
- 使用 ArkTS 提供的 UI 组件,如
Column
、Row
、Text
、TextInput
、Slider
、Checkbox
和Button
等,构建了设置面板的用户界面。 - 各种输入组件如文本输入框和滑动条分别用于获取用户输入的数值,并通过
onChange
事件实时更新config
对象中的相应配置项。
- 使用 ArkTS 提供的 UI 组件,如
-
设置项的实现:
- 文本输入框(
TextInput
)用于获取用户输入的延迟时间、图像宽度、高度和循环次数等。 - 滑动条(
Slider
)用于选择工作线程数和图像质量。滑动条的变化通过onChange
事件实时更新对应的配置项。 - 复选框(
Checkbox
)用于设置是否无限循环和是否防止抖动,这些布尔值直接影响配置对象中的相关属性。
- 文本输入框(
-
确定和取消按钮:
- 通过创建“确定”和“取消”按钮,允许用户保存或放弃当前的配置。点击“确定”按钮时,会将当前的
config
对象传递给settingsOK
回调函数,以便外部处理。点击“取消”按钮时,调用settingsCancel
回调函数以取消设置。
- 通过创建“确定”和“取消”按钮,允许用户保存或放弃当前的配置。点击“确定”按钮时,会将当前的
-
样式设置:
- 使用
.backgroundColor
、.border
、.width
和.height
等方法设置组件的样式属性,保证整个面板的外观一致性和用户体验。
- 使用
通过这些实现步骤,这段代码成功构建了一个配置面板,用户可以通过该面板设置各种图像处理参数,并通过点击按钮提交或取消这些设置。这个面板设计简洁、功能全面,可以适用于多种图像处理应用场景。
现在基本的设置完成了,然后可以将添加的静态图片导出成动画gif,这是使用了gif.js,下面是对这个库的详细描述:
概述:
GIF.js 是一个 JavaScript 库,用于在浏览器中创建和处理 GIF 动画。它允许用户通过代码生成高质量的 GIF 动画,并支持对 GIF 的每一帧进行精细控制。
主要功能:
- 创建 GIF 动画: GIF.js 允许用户将一系列图像或 Canvas 画布的内容合成为一个 GIF 动画。
- 帧控制: 可以控制每一帧的延迟时间、添加的顺序以及是否重复播放。
- 高效编码: GIF.js 使用了基于 Web Workers 的并行编码方式,以提高 GIF 生成的性能,特别是在处理较大的 GIF 文件时。
- Canvas 集成: 该库可以直接从 HTML5 Canvas 元素捕获图像数据,这使得它非常适合与 HTML5 Canvas 结合使用,生成动态的、基于浏览器的 GIF 动画。
- 压缩和质量设置: 支持调整输出 GIF 的质量和压缩率,以平衡文件大小和图像质量。
- 透明度支持: GIF.js 支持处理透明图像,能够生成具有透明背景的 GIF 动画。
应用场景:
- 动态内容生成: GIF.js 非常适合在网页上生成基于 Canvas 动画或用户绘制内容的动态 GIF 动画。
- 游戏开发: 在 Web 游戏中,可以使用 GIF.js 捕获和生成游戏回放或动画 GIF,以便玩家分享或保存。
- 社交媒体集成: 允许用户直接在网页应用中创建并分享 GIF 动画,适用于社交媒体平台和聊天应用程序。
- 数据可视化: 可用于将动态图表或数据可视化内容导出为 GIF 格式,以便在不支持动态内容的环境中展示。
本文的核心是createGIF函数,该函数利用了gif.js、fabric.js等技术,将多个静态图片保存为一个动画gif文件,下面是对这个函数的介绍。
代码中文注释与详细解释
function createGIF(config) {
return new Promise((resolve, reject) => {
// 创建一个新的 Canvas 元素,用于绘制图像
const canvas = document.createElement("canvas");
// 获取 Canvas 的 2D 渲染上下文
const ctx = canvas.getContext("2d");
let gif; // 将保存生成的 GIF 对象
var firstImage; // 保存第一张图像对象
// 使用 fabric.js 从第一个图像的 URL 加载图像
fabric.Image.fromURL(imagesArray[0], (img) => {
if (imagesArray.length === 0) {
return; // 如果没有图像,直接返回
}
if (!firstImage) {
firstImage = img; // 保存第一张图像
if (!config.width || !config.height) {
// 如果没有指定宽度或高度,则使用第一张图像的尺寸
config.width = firstImage.width;
config.height = firstImage.height;
}
// 设置 Canvas 的宽度和高度
canvas.width = config.width;
canvas.height = config.height;
// 初始化 GIF 对象
gif = new GIF({
workers: config.workers || 2, // 设置使用的 Web Worker 数量
quality: config.quality || 10, // 设置图像质量,值越低质量越高
workerScript: workerScriptURL, // 指定 worker 脚本的 URL
width: config.width,
height: config.height,
repeat: config.repeat || 0, // 设置动画的重复次数(0 为无限循环)
dither: config.dither || false, // 是否启用抖动处理
});
}
// 加载并处理所有图像,确保顺序
Promise.all(
imagesArray.map((src) => {
return new Promise((resolveImg, rejectImg) => {
// 使用 fabric.js 从每个图像的 URL 加载图像
fabric.Image.fromURL(
src,
(img) => {
// 应用图像效果
applyImageEffects(img, config.effects)
.then(() => {
resolveImg(img); // 确保效果应用后再解析
})
.catch(rejectImg);
},
{ crossOrigin: "anonymous" } // 允许跨域加载图像
);
});
})
)
.then((processedImages) => {
// 处理加载并应用效果后的所有图像
processedImages.forEach((img) => {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清除画布以防止图像重叠
processImage(img.getElement(), config, ctx); // 处理图像(例如缩放或裁剪)
// 将处理后的图像添加为 GIF 的一帧
gif.addFrame(ctx.canvas, {
delay: config.delay || 500, // 设置帧延迟时间
copy: true, // 是否复制帧数据
});
});
// 当 GIF 生成完成时触发的事件处理函数
gif.on("finished", function (blob) {
const reader = new FileReader();
// 当读取操作完成时触发的事件处理函数
reader.onloadend = () => {
// 将生成的 GIF 转换为 base64 编码
previewGif = reader.result;
// 解析 Promise,并返回生成的 GIF 的 base64 数据
resolve(reader.result);
};
// 将 Blob 对象读作一个 base64 编码的 Data URL
reader.readAsDataURL(blob);
});
// 开始渲染 GIF
gif.render();
})
.catch((error) => {
// 如果处理图像时出现错误,则拒绝 Promise 并返回错误信息
reject("Error processing images: " + error);
});
});
});
}
代码原理与详细解释
1. 创建与设置 Canvas
- 首先通过
document.createElement("canvas")
创建了一个新的 Canvas 元素,并通过canvas.getContext("2d")
获取其 2D 渲染上下文。这将用于在 Canvas 上绘制图像。
2. 加载第一张图像
- 代码使用
fabric.Image.fromURL
方法从给定的 URL 加载第一张图像,并检查是否提供了config
中的宽度和高度参数。如果没有指定宽度和高度,则使用第一张图像的尺寸作为 GIF 的宽度和高度。
3. 初始化 GIF 对象
- GIF 对象通过
new GIF()
初始化,并传递了多个配置参数,包括:workers
:用于指定生成 GIF 时使用的 Web Workers 数量,以提高性能。quality
:图像的质量设置,值越低质量越高,文件大小越小。width
和height
:设置生成 GIF 的宽度和高度。repeat
:指定 GIF 动画的重复次数。dither
:是否启用图像的抖动处理。
4. 加载和处理图像
- 使用
Promise.all
方法并结合fabric.Image.fromURL
加载所有图像,并对每一张图像应用指定的效果。加载完成并应用效果后,解析每张图像对象。 - 在
applyImageEffects
函数中,图像的各种效果(如灰度、模糊、亮度调整等)被应用到图像对象上。
5. 生成 GIF 帧
- 对每一张处理后的图像,首先清空 Canvas,然后根据配置对图像进行缩放或裁剪(通过
processImage
函数),最后将图像添加为 GIF 动画的一帧。
6. 完成 GIF 生成
- 在 GIF 生成完成后,触发
gif.on("finished")
事件,将生成的 GIF Blob 对象通过FileReader
转换为 base64 编码的 Data URL,并最终通过resolve
返回给调用者。
7. 渲染 GIF
- 调用
gif.render()
方法开始渲染 GIF 动画。渲染过程中,GIF.js 会使用 Web Workers 进行图像编码,最终生成完整的 GIF 文件。