作者: 凹凸曼-大力士


项目背景

羚珑平台在静态类的设计中,已经取得了相应的成绩。在这个基础上结合当前大环境,我们认为可以去做一些动态类的设计,将动画和音效转化为可储存,可移植,可复用的数据。从而用户进行创作的时候,可以通过相对很简单的方式去使用这些高品质的动画和效果。

视频编辑器解决了什么问题?

视频编辑器的主要作用是用户可以通过操作静态的PSD从而得到我们想要的动态设计效果。对比AE等复杂的视频编辑软件,学习成本大大降低,且动效的可复用性、移植性等也减轻了用户的工作量。

以下为设计效果:

开发实录

如何让你的静态PSD"动"起来?

参考 AE 的制作动画的过程,首先会预设剧本和分镜,其次规划好分镜中的镜头如何运动,角色如何运动,以及处理和规划素材。我们可以提炼出几个关键点:多场景、镜头移动(即场景整体的动效)、规划素材(图层内容出现时刻及时间长短灵活可控)
视频编辑器操作主要涉及功能点如下:

  • 多场景的切换与转场效果的融合,使视频效果更加生动灵活;
  • 场景动效以及动效参数的设置,减少了同类型动效的开发(如位移动效合并为一个),也打开了设计师对动效使用的想象力,收获额外的视频效果;
  • 图层操作,调整出现时刻及持续时间;

编辑器界面如下图:

状态管理

视频编辑器的实现主要分为 5 个部分,视频预览区、动效添加区、参数编辑区、图层操作区、场景操作区,如下图其他部分的每一个操作都会映射到视频预览区,且各个部分数据共享。除此之外,编辑器的每一步操作都需要被”记住“,便于编辑的人回退、还原其操作。

经分析会涉及到以下场景,如:

  • 预览区组件的状态需要共享
  • 其他操作区的变动会改变预览区组件的状态
  • 组件状态都需要可撤销/还原

我们可以采用 redux 集中管理状态以减少组件之间的数据流传递;对于撤销还原功能,我们可以采用 redux-undo,根据现有的 reducer 和配置对象,增强现有其撤消还原功能。

import ReduxUndo from 'redux-undo'
//定义原有的 reducer
const editReducer = (state = null, action) => {switch (action.type) {case VIDEO_INIT: {const { templates } = action.payloadreturn { templates }}case VIDEO_TPL_CLEAR: {return {}}
}//通过 ReduxUndo 增强 reducer 的可撤销功能
export const undoEditReducer = ReduxUndo(editReducer, {initTypes: [VIDEO_TPL_CLEAR],filter: function filterActions (action, currentState, previousHistory) {const { isUndoIgnore = false } = actionreturn !isUndoIgnore},groupBy: groupByActionTypes([SOME_ACTION]),/*自定义分组groupBy:(action, currentState, previousHistory) => {},*/
})

参数说明

  • initTypes:历史记录将根据初始化操作类型进行设置(重置)
  • filter:过滤器, 可以帮助过滤掉不想在撤消/重做历史中包含的操作;
  • groupBy:可以通过默认的 groupByActionTypes 方法将动作组合为单个撤消/重做步骤。也可以实现自定义分组行为,如果返回值不为 null,则新状态将按该返回值分组。如果下一个状态与上一个状态归为同一组,则这两个状态将在一个步骤中归为一组;如果返回值为 null,则 redux-undo 不会将下一个状态与前一个状态分组。

使用 store.dispatch() 和 Undo/Redo Actions 对你的状态执行撤消/重做操作

import { ActionCreators } from 'redux-undo'
export const undo = () => (dispatch, getState) => {dispatch(ActionCreators.undo())
}
export const redo = () => (dispatch, getState) => {dispatch(ActionCreators.redo())
}
export const recovery = () => (dispatch, getState) => {dispatch(ActionCreators.jumpToPast(0))dispatch(ActionCreators.clearHistory())
}

总结

对于状态管理,首先我们可以从以下几点考虑是否需要引入redux、mobx等工具:

  • 状态是否被多个组件或者跨页面共享;
  • 组件状态需要跨越生命周期;
  • 状态需要如持久化,可恢复/撤销等操作。
    在使用redux管理状态时,避免将所有状态抽离至redux store中,如
  • 组件的私有状态;
  • 组件状态传递层级较少;
  • 当组件被unmount后可以销毁的数据等
    原则上是能放在组件内部就放在组件内部。其次为了状态的可读性和可操作性,在状态结构设计前,需要理清楚各个数据对象的关系,平衡数据获取及操作复杂度,推荐扁平化数据结构以减少嵌套和数据冗余。

图层交互

在使用编辑器的过程中,图层的交互操作是最多最频繁的,我们参考了常用的客户端视频编辑软件 AE、Final Cut 的交互,尽可能在 Web 上提供用户操作的便利性及图层可视化,具体效果如下:

梳理图层操作需求,主要包含:

  • 图层轨道需要伸缩(调整图层持续时间)
  • 图层上的动效轨道可以单独伸缩(调整动效持续时间)
  • 图层轨道需要左右移动,且动效轨道跟随移动(调整出现的时刻)
  • 动效轨道可以单独左右移动(调整动效出现的时刻)
  • 不同图层轨道可以上下调整顺序,动效轨道跟随图层轨道移动(调整图层顺序)
  • 拖动时显示不同的外观

初始的时候首先考虑到需要移动图层顺序,我们基于 react-sortable-hoc 实现了基本的图层顺序拖曳移动 , 但是对于图层的拉伸、左右拖动处理需要自定义鼠标事件进行处理,并需要自定义计算控制图层的移动,而且最初没有考虑到拖动过程中拖动源的外观需要调整,最终,我们放弃这种实现。我们需要一个可定制化程度更高的拖曳组件,经过一番比较后,我们最终选定了 react-dnd 拖拽组件,查看其官方说明:

可帮助您构建复杂的拖放界面,同时保持组件的分离;且适用于拖动时在应用程序的不同部分之间传输数据,更完美的是组件可以响应拖放事件更改其外观和应用程序状态。

详细说明下,react-dnd 建立在 HTML5 拖放 API 之上,它可以对已拖动的 DOM 节点进行屏幕快照,并直接将其用作“拖动预览”, 简化了我们在光标移动时进行绘制的操作。不过,HTML5 拖放 API 也有一些缺点。它在触摸屏上不起作用,并且在 IE 上提供的自定义机会少于其他浏览器。这就是为什么在 react-dnd 中以可插入方式实现 HTML5 拖放支持的原因,你也可以不使用它,根据触摸事件,鼠标事件等自己来编写其他实现。

下面,我们从外到内,介绍基本的实现。

场景层面

引入所需组件

import { DndProvider } from 'react-dnd'
import HTML5Backend from 'react-dnd-html5-backend'

将 DndProvider 放在整个场景的外层,设置 backend 为 HTML5Backend

<DndProvider backend={HTML5Backend}> <TemplateViewer   // ----- 单个场景展示组件template={tpl}handleLayerSort={handleLayerSort}onLayerDrop={onLayerDrop}onLayerStretch={onLayerStretch}/><CustomDragLayer />  // --- 自定义拖拽预览图层
</DndProvider>

TemplateViewer 里包含不同类型的图层组件。每个图层组件都提供一个纯渲染组件的方法 renderLayerContent,大致结构如下:

export function renderLayerContent (layer) {return <div style={{...}}>...</div>
}export default function XxxxLayerComponent (layer) {...return <div>{renderLayerContent(layer)}</div>
}

CustomDragLayer 里根据当前拖拽的对象的组件类型,调用相应 renderLayerContent 绘制拖拽可视内容,以实现拖拽前后的视图一致。

图层层面


图层可以上下拖动,也可以左右拖动,意味它本身即是拖拽源,也是放置的目标。

为了区分拖拽的目的,我们定义了两个拖拽源

  const [{ isHorizontalDragging }, horizontalDrag, preview] = useDrag({item: {type: DragTypes.Horizontal,},collect: monitor => ({isHorizontalDragging: monitor.isDragging(),}),})const [{ isVerticalDragging }, verticalDrag, verticalPreview] = useDrag({item: {type: DragTypes.Vertical,},collect: monitor => ({isVerticalDragging: monitor.isDragging(),}),})

在放置处理中根据拖拽类型进行判断处理

  const [, drop] = useDrop({accept: [DragTypes.Horizontal, DragTypes.Vertical],drop (item, monitor) {// 处理左右拖动},hover: throttle(item => {// 处理上下排序}, 300),})

将定义好的拖动源和放置目标关联 DOM 。最外层 DIV 为图层可拖动区域即放置目标,然后依次为水平拖拽层,垂直拖拽层

<div ref={drop}> // --- 放置目标 DOM<div ref={verticalPreview}><div ref={horizontalDrag}> // --- 水平拖拽 DOM<div ref={verticalDrag}> // --- 垂直拖拽 DOM<Icon type='drag'/></div>/* 图层内容展示 */<div>{renderLayerContent(layer)}</div></div></div>
</div>

以上关于图层上下拖动、左右拖动的大体框架已经实现。

上下拖动排序时,为了拖动过程中不展示拖动源只保留生成的屏幕快照,可以根据当前的拖动状态将拖动源的透明度设置为 0

<div ref={drop}> // --- 放置目标 DOM<divref={verticalPreview}style={{ opacity: isVerticalDragging ? 0 : 1 }}>...</div>
</div>

水平拖动时,设置拖动源半透明,处理方式与上下拖动时同理。

图层内

图层内有两个区域,下方区域可通过左右两端的操作点进行拉伸,上方区域可以在下方区域的宽度内左右移动以及同样通过左右两端的操作点进行拉伸。
移动的实现方式前面已经介绍过就不重复了,针对拉伸的操作,我们封装一个 Stretch 类来统一处理

function Stretch ({children,left,width,onStretchEnd,onStretchMove,
}) {function handleMouseDown (align) {// 计算偏移}return (<div>{children}<divclassName={classnames(styles.stretch, styles.stretchHead)}onMouseDown={handleMouseDown('head')}/><divclassName={classnames(styles.stretch, styles.stretchEnd)}onMouseDown={handleMouseDown('end')}/></div>)
}

将需要支持拉伸的区域作为作为 Stretch 的 children 传递进来

<div><div>{motions.map((motion, i) => <Stretch key={i}>{/* 上方某个区域 */}</Stretch>)}</div><div><Stretch>{/* 下方区域 */}</Stretch></div>
</div>

体验优化

添加快捷键

整个编辑器内容比较的多,对频繁的操作,我们可以保留常用快捷键的操作习惯。如空格播放、delete 删除等等,该功能我们可以使用 react-hot-keys 实现。

首先引入该快捷键库,然后指定绑定的快捷键,添加事件处理。

import Hotkeys from 'react-hot-keys'<HotkeyskeyName='space'onKeyDown={(keyName, e) => {e.preventDefault()play()}}
/>

文本转 SVG

另外图层内容展示时有个小技巧,产品需求中文案图层平铺展示。可怜我最初竟然是通过文本长度以及轨道长度计算出文本展示次数,然后再放到 push 到节点中。经大佬改造后才明白可以将文本转化为 SVG 然后以背景图展示,真香!

<divclassName={styles.contentText}style={{backgroundImage: `url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' version='1.1'     width='${size(layer.text) * 12 + 15}px' height='35px'><text x='10' y='22' fill='black' font-size='12'>${layer.text}</text></svg>")`,}}
/>

实现效果:

项目总结

本文讲述了视频编辑器中操作区主要模块的处理。关于状态管理,我们主要需要明确引入管理工具的是否必要以及使用状态管理工具后是否所有状态都必须移入store中等等。另外对于复杂的图层拖拽功能,要像剥洋葱一样,先层层拆解,从而层层完善其结构。
对项目而言,拿到需求后,我们从整体到局部进行分析,优先确定整体的框架、核心功能的实现方式等,进而考虑如何提高用户体验度。需求分清主次,以便于我们排列优先级从而开发提高效率。

参考资料

[1] react-dnd: https://react-dnd.github.io/react-dnd/about


欢迎关注凹凸实验室博客:aotu.io

或者关注凹凸实验室公众号(AOTULabs),不定时推送文章:

羚珑视频编辑器开发总结相关推荐

  1. c语言开发视频剪辑,羚珑视频编辑器开发总结

    作者:凹凸曼-大力士 项目背景 羚珑平台在静态类的设计中,已经取得了相应的成绩.在这个基础上结合当前大环境,我们认为可以去做一些动态类的设计,将动画和音效转化为可储存,可移植,可复用的数据.从而用户进 ...

  2. 凹凸技术揭秘·羚珑页面可视化·成长蜕变之路

    作者: 凹凸曼 前言 京东零售集团 · 用户体验设计部打造的「羚珑智能设计平台」于 2019 年 5 月为内部运营及商家推出了智能页面设计工具,羚珑智能页面设计是一款在线可视化页面搭建平台,拥有在线搭 ...

  3. Android视频编辑器(一)通过OpenGL预览、录制视频以及断点续录等

    前言 如今的视频类app可谓是如日中天,火的不行.比如美拍.快手.VUE.火山小视频.抖音小视频等等.而这类视频的最基础和核心的功能就是视频录制和视频编辑功能.包括了手机视频录制.美白.加滤镜.加水印 ...

  4. 【新项目开发】vue3+ts+elementPlus+ffmpegjs开发纯web端的视频编辑器

    新项目开发的流程 当在项目中使用新技术时,我们应该首先进行调研,了解其特点和使用方法.在实现功能时,我们可以采用最简单的方式,而不必过于关注项目的设计和结构.一旦掌握了新技术,我们可以根据其API属性 ...

  5. C++实战学习OpenCV3.2+QT5+ffmpeg实战开发视频编辑器视频教程

    提前地址提取码:u126 ├──01 介绍 │ ├──attached_files │ │ ├──002 opencv源码在windows下载编译安装 │ │ │ └──opencv3.2Linux. ...

  6. 乐视云视频 接口开发 结合百度编辑器

    乐视云视频提供上传接口和删除接口,结合后台原本的百度编辑器,实现了从百度编辑器中直接上传视频,删除文章时一同删除云端视频等功能 上图,实现从百度编辑器中上传视频,上传完成后将视频插入到当前光标的位置, ...

  7. 开发C++视频编辑器 OpenCV3.2 FFmpeg Qt5实战

    ├─01 介绍 │  │  001 介绍.mp4 │  │  002 opencv源码在windows下载编译安装.mp4 │  │  003 Ubuntu下编译opencv源码.mp4 │  │   ...

  8. 凹凸技术揭秘·羚珑智能设计平台·逐梦设计数智化

    1.简介- 羚珑智能设计平台是由京东零售集团用户体验设计部[1]打造的在线设计服务平台,专注于泛零售领域的设计,帮助客户解决日常经营过程中所碰到的各类设计需求,例如商品上新时的商品主图视频.各种节日大 ...

  9. 游戏引擎开发和物理引擎_视频游戏开发的最佳游戏引擎

    游戏引擎开发和物理引擎 In this article, we'll look at some of the most popular game engines for video game deve ...

最新文章

  1. opencv根据直线方程求交点坐标
  2. mvc重定向方式详解
  3. VC++ 使用预编译头
  4. signalr 连接数量的限制_LED灯珠常用的连接方式优缺点分析!
  5. Python引用模块和查找模块路径
  6. javaweb-服务器输出字符数据到浏览器
  7. ITK:图像阈值演示可用的阈值算法
  8. win32 api 文件操作!
  9. 时间序列异常检测 EGADS Surus iForest
  10. 联想台式计算机排行榜,联想电脑品牌机排名介绍
  11. mybatis插入时间_深入分析MyBatis源码
  12. Intellij IDEA中程序播放音乐 没声音
  13. 投票群体案例介绍(36)
  14. 物联网LoRa系列-25:LoRa终端--LoRaWAN协议简介与LoRa WAN终端软件选择
  15. 《算法导论》第三版第4章 分治策略 练习思考题 个人答案
  16. 交换机怎么用计算机配置,配置交换机,教您怎么配置交换机
  17. Java微服务+分布式+全栈项目(一)---->项目介绍+MyBatis-Plus入门
  18. 嵌入式系统开发 (复习笔记)
  19. 索尼电视A90J、A80J怎么安装蚂蚁市场下载第三方软件方法
  20. 中密歇根大学计算机专业,密歇根大学计算机工程排名,真是要细心看懂

热门文章

  1. 虚拟机无法连接虚拟设备sata0:1
  2. Matlab - Solidworks 机器人建模(4)—— 如何把SolidWorks模型导入到Matlab (Simscape模型)
  3. 安卓Android与H5双向交互MathJax展示数学公式(源码+解析)
  4. Linux环境准备五---VMWare打开CentOS虚拟机报错VT(长模式不兼容)等错误的解决方案
  5. PowerShell install 一键部署VMware_Workstation
  6. HDU 5547 数独(DFS变形+4*4数独)
  7. 日语学习之——五十音图及单词(3)
  8. 消息队列RabbitMQ基本使用(Java代码实现)
  9. 国外著名大学的开放资源
  10. 网易考拉API,根据ID取产品详情 OneBound数据