本文主要介绍前端如何实现动画转场效果,帮助大家更好的理解和学习前端开发网页。感兴趣的朋友可以了解一下。
目录
传统转场动画css转场动画js动画传统和Vue/React框架下转场动画转场组件对比
模块/过渡
React数据驱动动画中的过渡动画
简介
动画这个概念很宽泛,涉及各个领域。这里我们把范围缩小到前端web应用层面,游戏领域的Animate就更不用说了。一切从最简单的开始。
目前大部分web应用都是基于框架开发的,比如Vue、React等。它们都是基于数据驱动的视图,那么我们就来比较一下在这些框架存在之前,我们是如何实现动画或者转场效果的,然后用数据驱动来实现。
传统过渡动画
动画对体验有非常重要的作用,但对于很多开发者来说,可能是非常薄弱的环节。css3出现后,很多初学者最常用的动画转场可能就是css3的能力了。
Css过渡动画
启动css转场动画很简单,写转场属性就行,下面写个演示。
div id='app' class='normal'/div。正常{
宽度:100px
高度:100px
背景色:红色;
过渡:全0.3s
}。正常:悬停{
背景色:黄色;
宽度:200px
高度:200px
}
效果还是很好的。css3的过渡基本满足了大部分动画需求。如果没有,还有真正的css3动画。
动画-css
著名的css动画库,谁知道谁用。
无论是css3转场还是css3动画,我们简单的通过切换类名来使用。如果要做回调处理,浏览器还提供了ontransitionend、onanimationend等动画帧事件,可以通过js接口进行监控。
var El=document . query selector(# app)
El . addevent listener(' transition start ',()={
console.log('过渡开始')
})
El . addevent listener(' transition end ',()={
console.log(“过渡结束”)
})
好了,这就是css动画的基础。大部分动画转场需求也可以通过js封装来实现,但局限性是只能控制css支持的属性动画,控制力还是略弱。
Js动画
毕竟js是自定义编码程序,对动画的控制能力很强,可以实现css不支持的各种效果。那么js动画的基础是什么呢?
简单来说,所谓动画就是不断更新时间轴上某个元素的属性,然后交给浏览器重新绘制,视觉上就变成了动画。废话少说,还是先来个栗子:
div id='app' class='normal'/div
//Tween只是一个慢函数。
var El=document . query selector(# app)
var time=0,begin=0,change=500,duration=1000,fps=1000/60;
函数startSport() {
var val=补间。Elastic.easeInOut(时间、开始、变化、持续时间);
El . style . transform=' translate x(' val ' px)';
if (time=duration) {
时间=fps
}否则{
Console.log('动画结束并重新开始')
时间=0;
}
setTimeout(()={
startSport()
},fps)
}
startSport()
通过setTimeout或requestAnimation可以实现时间轴上属性的不断更新。至于补间点动功能,是一个类似插值的概念。给定一系列变量,可以得到区间上任意时刻的值。纯数学公式,几乎所有动画帧都可以用。详情可参考张新旭的Tween.js。
好了,这个极简demo也是js动画的核心基础。可以看到我们通过程序完美的控制了转场值的生成过程,其他所有复杂的动画机制都是这种模式。
与传统Vue/React框架相比
通过前面的例子,无论是css转场还是js转场,我们都是直接获取dom元素,然后对dom元素进行属性操作。
Vue/React都引入了虚拟数字正射影像图的概念,数据驱动视图,我们尽量不去操作多姆,只控制数据,那么我们如何在数据层面驱动动画呢?
某视频剪辑软件框架下的过渡动画
可以先看一遍文档
某视频剪辑软件过渡动画
我们就不讲如何使用了,我们来分析一下某视频剪辑软件提供的过渡组件是如何实现动画过渡支持的。
过渡组件
先看过渡组件代码,路径“src/platforms/web/runtime/components/transition。js "
核心代码如下:
//辅助函数,复制小道具的数据
导出函数extractransitiondata(comp:Component):Object {
常量数据={}
常数选项:ComponentOptions=comp .$选项
//道具
对于(选项。道具数据中的常量键){
数据[关键字]=组件[关键字]
}
//事件。
常量侦听器:对象=选项. parent listeners _ parent
对于(侦听器中的常量键){
data[camelize(key)]=listeners[key]
}
返回数据
}
导出默认值{
名称:"过渡",
道具:过渡道具,
摘要:真,//抽象组件,意思是不会真实渲染成多姆,辅助开发
渲染(h:函数){
//通过时间获取到真实渲染元素儿童
让孩子:任何=这个. slots.default
const mode: string=this.mode
const raw child:VNode=children[0]
//添加唯一键
//组件实例。该键将用于删除挂起的离开节点
//进入过程中。
const id:string=` _ _ transition-$ { this ._ uid }-` 0
child.key=getKey(id)
:child.key
//数据上注入过渡属性,保存通过小道具传递的数据
const data:Object=(child。数据| |(子。data={ }).transition=extract transitiondata(this)
const oldRawChild: VNode=this ._vnode
const old child:VNode=getRealChild(oldRawChild)
//对动态过渡很重要!
const old Data:Object=old child。数据。过渡=扩展({ },数据)
//处理转换模式
if (mode==='out-in') {
//休假结束时返回占位符节点和队列更新
这个。_离开=真
mergeVNodeHook(oldData,' afterLeave ',()={
这个。_离开=假
这个. forceUpdate()
})
返回占位符(生孩子)
} else if (mode==='in-out') {
让延迟离开
const perform leave=()={ delayedLeave()}
mergeVNodeHook(数据,'回车后,执行离开)
mergeVNodeHook(数据,' enterCancelled ',performLeave)
mergeVNodeHook(oldData,' delayLeave ',leave={ delayedLeave=leave })
}
返回生孩子
}
}
可以看到,这个组件本身功能比较简单,就是通过时间拿到需要渲染的元素孩子们,然后把过渡的小道具属性数据复制到数据的翻译属性上,供后续注入生命周期使用,mergeVNodeHook就是做生命周期管理的。
模块/过渡
接着往下看生命周期相关,路径:
src/平台/web/运行时/模块/过渡。射流研究…
先看默认导出:
function _enter (_: any,vnode: VNodeWithData) {
if (vnode.data.show!==true) {
输入(虚拟节点)
}
}
在浏览器中导出默认值?{
创建:_回车,
激活:_回车,
remove (vnode: VNode,rm: Function) {
if (vnode.data.show!==true) {
离开(vnode,rm)
}
}
} : {}
这里inBrowser就当做没错,因为我们分析的是浏览器环境。
接着看进入和离开函数,先看输入:
导出函数addTransitionClass (el: any,cls: string) {
const transitionClasses=el ._ transition class | |(El ._transitionClasses=[])
如果(过渡类。(cls)0的索引){
过渡类。推送(cls)
addClass(el,cls)
}
}
导出函数removeTransitionClass (el: any,cls: string) {
如果(厄尔._transitionClasses) {
移除(厄尔._ transitionClasses,cls)
}
removeClass(el,cls)
}
导出函数enter (vnode: VNodeWithData,toggleDisplay:()=void) {
const el: any=vnode.elm
//现在呼叫离开回拨
if (isDef(el ._leaveCb)) {
埃尔. leaveCb.cancelled=true
埃尔. leaveCb()
}
//上一步注入数据的过渡数据
const data=解析转换(vnode。数据。过渡)
if (isUndef(data)) {
返回
}
/*伊斯坦布尔忽略if */
if (isDef(el ._enterCb) || el.nodeType!==1) {
返回
}
常数{
css,
类型,
输入类别,
enterToClass,
输入ActiveClass,
外观类,
外观类,
appearActiveClass,
在进入之前,
进入,
回车后,
回车取消,
在出现之前,
出现,
出现后,
上诉被取消,
期间
}=数据
假设上下文=activeInstance
设transitionNode=activeInstance .$vnode
const isAppear=!语境. isMounted ||!vnode.isRootInsert
如果(isAppear!出现出现!=='') {
返回
}
//获取合适的时机应该注入的类名
const start class=is appear出现类
?外观类
:输入类别
const active class=is appear appear active class
?外观类别
:输入ActiveClass
const to class=is appear出现class
?外观类
:enterToClass
const beforeEnterHook=isAppear
?(出现之前||进入之前)
:输入前
const enterHook=isAppear
?(typeof appear==='function '?出现:输入)
:输入
const afterEnterHook=isAppear
?(afterAppear || afterEnter)
:回车后
const entercanceledhook=is出现
?(上诉已取消||回车已取消)
:回车取消
const explicit interduration:any=to number(
isObject(持续时间)
?持续时间。输入
:持续时间
)
const expectsCSS=css!==假的!isIE9
const userwontscontrol=gethook arguments length(enter hook)
//过渡结束之后的回调处理,删掉进入时的班级
常数cb=el ._enterCb=once(()={
如果(需要scss){
removeTransitionClass(el,toClass)
removeTransitionClass(el,activeClass)
}
如果(cb .已取消){
如果(需要scss){
removeTransitionClass(el,startClass)
}
entercanceledhook
}否则{
后进入钩
}
埃尔. enterCb=null
})
//dom进入时,添加开始上课进行过渡
前输入钩前输入钩(el)
如果(需要scss){
//设置过渡开始之前的默认样式
addTransitionClass(el,startClass)
addTransitionClass(el,activeClass)
//浏览器渲染下一帧删除默认样式,添加toClass
//添加结束事件监听,回调就是上面的可换股债券
下一帧(()={
removeTransitionClass(el,startClass)
如果(!cb。已取消){
addTransitionClass(el,toClass)
如果(!userwontscontrol){
if(是有效的持续时间(明确的持续时间)){
setTimeout(cb,explicitEnterDuration)
}否则{
当过渡结束时(el,type,cb)
}
}
}
})
}
if (vnode.data.show) {
toggleDisplay toggleDisplay()
回车键回车键
}
如果(!expectsCSS!userwontscontrol){
cb()
}
}
进入里使用了一个函数当过渡结束时,其实就是监听过渡或者动画结束的事件:
出口信函transitionEndEvent=' transitionend '
导出let动画结束事件='动画结束'
过渡结束时导出函数(
el:元素,
预期类型:字符串,
cb:功能
) {
const { type,timeout,propCount }=getTransitionInfo(El,expectedType)
如果(!类型)返回cb()
常数事件:string=type===过渡?过渡事件:动画事件
let ended=0
const end=()={
el.removeEventListener(event,onEnd)
cb()
}
const onEnd=e={
if (e.target===el) {
if ( ended=propCount) {
结束()
}
}
}
setTimeout(()={
if (ended propCount) {
结束()
}
},超时1)
el.addEventListener(event,onEnd)
}
好吧,到了这里,根据上面源代码的注释分析,我们可以发现:
某视频剪辑软件先是封装了一些列操作数字正射影像图类名的辅助方法addClass/removeClass等。
然后在生命周期回车钩之后,马上设置了startClass也就是输入类别的默认初始样式,还有activeClass
紧接着在浏览器下一帧下一帧,移除了startClass,添加了toClass,并且添加了过渡动画的结束事件监听处理
监听到结束事件之后,调动cb,移除了toClass和activeClass
离开的过程和进入的处理过程是一样,只不过是反向添加移除类名
结论:Vue的动画过渡处理方式和传统数字正射影像图本质上是一样,只不过融入了某视频剪辑软件的各个生命周期里进行处理,本质上还是在数字正射影像图添加删除的时机进行处理
反应里的过渡动画
噢,我们翻篇了反应的文档,也没有发现有过渡动画的处理。嘿,看来官方不原生支持。
但是我们可以自己实现,比如通过使用状态维护一个状态,在提供;给予里根据状态进行类名的切换,但是复杂的该怎么办?
所幸在社区找到了一个轮子插件反应-过渡-基团
嗯,直接贴源码,有了前面某视频剪辑软件的分析,这个非常容易理解,反而更简单:
类转换扩展了做出反应.组件{
静态上下文类型=TransitionGroupContext
构造函数(属性,上下文){
超级(道具,上下文)
让parentGroup=上下文
让出现=
parentGroup!parentGroup.isMounting?道具。进入:道具。出现
让初始状态
this.appearStatus=null
if (props.in) {
如果(出现){
初始状态=已退出
this.appearStatus=进入
}否则{
初始状态=已输入
}
}否则{
如果(道具。un mount onexit | |道具。onenter山){
初始状态=未安装
}否则{
初始状态=已退出
}
}
这个。state={ status:初始状态}
this.nextCallback=null
}
//初始数字正射影像图的时候,更新默认初始状态
componentidmount(){
this.updateStatus(true,this.appearStatus)
}
//数据更新的时候,更新对应的状态
componentDidUpdate(prevProps) {
让nextStatus=null
如果(prevProps!==this.props) {
const { status }=这个。状态
如果(this.props.in) {
如果(状态!==进入状态!==已输入){
nextStatus=进入
}
}否则{
如果(状态===输入||状态===输入){
nextStatus=正在退出
}
}
}
this.updateStatus(false,nextStatus)
}
updateStatus(mounting=false,nextStatus) {
if (nextStatus!==null) {
//nextStatus将总是进入或退出。
this.cancelNextCallback()
if(下一状态===enter){
this.performEnter输入输入(安装)
}否则{
this.performExit()
}
} else if(这个。道具。联合国一号离开这里。状态。状态===已退出){
this.setState({状态:已卸载})
}
}
执行输入(装载){
const { enter }=this.props
const appearing=this.context?这个。语境。是挂载:挂载
const [maybeNode,可能出现]=this。道具。noderef
?[出现]
:[ReactDOM.findDOMNode(this),出现]
const timeouts=this.getTimeouts()
常量输入超时=出现?超时。出现:超时。输入
//没有回车动画直接跳到回车
//如果我们安装并运行它,这意味着必须设置
如果((!安装!enter) || config.disabled) {
这个。safesetstate({ status:enter },()={
this.props.onEntered(maybeNode)
})
返回
}
this.props.onEnter(可能节点,可能出现)
this.safeSetState({ status:正在输入},()={
this.props.onEntering(也许节点,也许出现)
这个。ontranspotionend(输入超时,()={
这个。safesetstate({ status:enter },()={
this.props.onEntered(可能节点,可能出现)
})
})
})
}
performExit() {
const { exit }=this.props
const timeouts=this.getTimeouts()
const maybe node=this。道具。noderef
?不明确的
:ReactDOM.findDOMNode(this)
//没有退出动画,直接跳到已退出
如果(!退出||配置。禁用){
这个。安全设置状态({状态:已退出},()={
这个。道具。一个退出(可能是节点)
})
返回
}
this.props.onExit(maybeNode)
this.safeSetState({状态:正在退出},()={
this.props.onExiting(maybeNode)
这个。ontransitionend(超时。退出,()={
这个。安全设置状态({状态:已退出},()={
这个。道具。一个退出(可能是节点)
})
})
})
}
cancelNextCallback() {
if (this.nextCallback!==null) {
this.nextCallback.cancel()
this.nextCallback=null
}
}
safeSetState(nextState,callback) {
//这不应该是必要的,但是有一些奇怪的竞争条件
//测试中的设置状态回调和卸载,因此请始终确保
//卸载后,我们可以取消任何挂起的设置状态回调。
回拨=这个。setnextcallback(回调)
this.setState(nextState,callback)
}
setNextCallback(callback) {
让活动=真
this.nextCallback=event={
如果(活动){
活动=假
this.nextCallback=null
回调(事件)
}
}
this.nextCallback.cancel=()={
活动=假
}
返回this.nextCallback
}
//监听过渡结束
onTransitionEnd(超时,处理程序){
this.setNextCallback(处理程序)
const node=this.props.nodeRef
?this.props.nodeRef.current
:ReactDOM.findDOMNode(this)
const doesNotHaveTimeoutOrListener=
timeout==null!this.props.addEndListener
如果(!node | | doesNotHaveTimeoutOrListener){
setTimeout(this.nextCallback,0)
返回
}
if (this.props.addEndListener) {
const [maybeNode,maybextcallback]=this。道具。noderef
?[this.nextCallback]
:[node,this.nextCallback]
这个。道具。addendlistener(可能是node,可能是NextCallback)
}
如果(超时!=null) {
setTimeout(this.nextCallback,Timeout)
}
}
render() {
常量状态=this.state .状态
如果(状态===已卸载){
返回空
}
常数{
孩子们,
//筛选"过渡"的属性
in: _in,
mountOnEnter: _mountOnEnter,
unmountOnExit: _unmountOnExit,
出现:_出现,
回车:_回车,
退出:_退出,
超时:_timeout,
addEndListener: _addEndListener,
onEnter: _onEnter,
onEntering: _onEntering,
onEntered: _onEntered,
onExit: _onExit,
onExiting: _onExiting,
onExited: _onExited,
nodeRef: _nodeRef,
.儿童道具
}=这个。道具
返回(
//允许嵌套转换
过渡组上下文.提供程序值={null}
{子类型==='函数'
?儿童(状态、儿童道具)
:React.cloneElement(React .Children.only(children),childProps)}
/TransitionGroupContext .供应者
)
}
}
可以看到,和某视频剪辑软件是非常相似的,只不过这里变成了在反应的各个生命周期函数了进行处理。
到了这里,我们会发现不管是某视频剪辑软件的过渡组件,还是反应这个跃迁群组件,着重处理的都是钢性铸铁属性的动画。
数据驱动的动画
而实际场景中总是会遇到钢性铸铁无法处理的动画,这个时候,可以有两种解决方案:
通过裁判员获取多姆,然后采用我们传统的射流研究…方案。
通过状态状态维护绘制数字正射影像图的数据,不断通过设置状态更新状态类驱动视图自动刷新
以上就是前端如何实现动画过渡效果的详细内容,更多关于前端实现动画过渡效果的资料请关注我们其它相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。