简介
动画这个概念非常宽泛,涉及各个领域,这里我们把范围缩小到前端网页应用层面上,不用讲游戏领域的Animate,一切从最简单的开始。
目前大部分网页应用都是基于框架开发的,比如Vue,React等,它们都是基于数据驱动视图的,那么让我们来对比一下,还没有这些框架的时候我们如何实现动画或者过渡效果,然后使用数据驱动又是如何实现的。
传统过渡动画
动画效果对体验有着非常重要的效果,但是对于很多开发者来讲,可能是个非常薄弱的环节。在css3出现之后,很多初学者最常用的动画过渡可能就是css3的能力了。
css过渡动画
css启动过渡动画非常简单,书写transition属性就可以了,下面写一个demo
<div id="app" class="normal"></div>
.normal { width: 100px; height: 100px; background-color: red; transition: all 0.3s; } .normal:hover { background-color: yellow; width: 200px; height: 200px; }
效果还是很赞的,css3的transition基本满足了大部分动画需求,如果不满足还有真正的css3 animation。
animate-css
大名鼎鼎的css动画库,谁用谁知道。
不管是css3 transition 还是 css3 animation,我们简单使用都是通过切换class类名,如果要做回调处理,浏览器也提供了 ontransitionend , onanimationend等动画帧事件,通过js接口进行监听即可。
var el = document.querySelector('#app') el.addEventListener('transitionstart', () => { console.log('transition start') }) el.addEventListener('transitionend', () => { console.log('transition end') })
ok,这就是css动画的基础了,通过js封装也可以实现大部分的动画过渡需求,但是局限性在与只能控制css支持的属性动画,相对来说控制力还是稍微弱一点。
js动画
js毕竟是自定义编码程序,对于动画的控制力就很强大了,而且能实现各种css不支持的效果。 那么 js 实现动画的基础是什么?
简单来讲,所谓动画就是在 时间轴上不断更新某个元素的属性,然后交给浏览器重新绘制,在视觉上就成了动画。废话少说,还是先来个栗子:
<div id="app" class="normal"></div>
// Tween仅仅是个缓动函数 var el = document.querySelector('#app') var time = 0, begin = 0, change = 500, duration = 1000, fps = 1000 / 60; function startSport() { var val = Tween.Elastic.easeInOut(time, begin, change, duration); el.style.transform = 'translateX(' + val + 'px)'; if (time <= duration) { time += fps } else { console.log('动画结束重新开始') time = 0; } setTimeout(() => { startSport() }, fps) } startSport()
在时间轴上不断更新属性,可以通过setTimeout或者requestAnimation来实现。至于Tween缓动函数,就是类似于插值的概念,给定一系列变量,然后在区间段上可以获取任意时刻的值,纯数学公式,几乎所有的动画框架都会使用,想了解的可以参考张鑫旭的Tween.js
OK,这个极简demo也是js实现动画的核心基础了,可以看到我们通过程序完美的控制了过渡值的生成过程,所有其他复杂的动画机制都是这个模式。
传统和Vue/React框架对比
通过前面的例子,无论是css过渡还是js过渡,我们都是直接获取到 dom元素的,然后对dom元素进行属性操作。
Vue/React都引入了虚拟dom的概念,数据驱动视图,我们尽量不去操作dom,只控制数据,那么我们如何在数据层面驱动动画呢?
Vue框架下的过渡动画
可以先看一遍文档
Vue过渡动画
我们就不讲如何使用了,我们来分析一下Vue提供的transition组件是如何实现动画过渡支持的。
transition组件
先看transition组件代码,路径 “src/platforms/web/runtime/components/transition.js”
核心代码如下:
// 辅助函数,复制props的数据 export function extractTransitionData (comp: Component): Object { const data = {} const options: ComponentOptions = comp.$options // props for (const key in options.propsData) { data[key] = comp[key] } // events. const listeners: "htmlcode">function _enter (_: any, vnode: VNodeWithData) { if (vnode.data.show !== true) { enter(vnode) } } export default inBrowser "htmlcode">export function addTransitionClass (el: any, cls: string) { const transitionClasses = el._transitionClasses || (el._transitionClasses = []) if (transitionClasses.indexOf(cls) < 0) { transitionClasses.push(cls) addClass(el, cls) } } export function removeTransitionClass (el: any, cls: string) { if (el._transitionClasses) { remove(el._transitionClasses, cls) } removeClass(el, cls) } export function enter (vnode: VNodeWithData, toggleDisplay: "htmlcode">export let transitionEndEvent = 'transitionend' export let animationEndEvent = 'animationend' export function whenTransitionEnds ( el: Element, expectedType: "_blank" href="https://github.com/reactjs/react-transition-group" rel="external nofollow" >react-transition-group
嗯,直接贴源码,有了前面Vue的分析,这个非常容易理解,反而更简单:class Transition extends React.Component { static contextType = TransitionGroupContext constructor(props, context) { super(props, context) let parentGroup = context let appear = parentGroup && !parentGroup.isMounting ? props.enter : props.appear let initialStatus this.appearStatus = null if (props.in) { if (appear) { initialStatus = EXITED this.appearStatus = ENTERING } else { initialStatus = ENTERED } } else { if (props.unmountOnExit || props.mountOnEnter) { initialStatus = UNMOUNTED } else { initialStatus = EXITED } } this.state = { status: initialStatus } this.nextCallback = null } // 初始dom的时候,更新默认初始状态 componentDidMount() { this.updateStatus(true, this.appearStatus) } // data更新的时候,更新对应的状态 componentDidUpdate(prevProps) { let nextStatus = null if (prevProps !== this.props) { const { status } = this.state if (this.props.in) { if (status !== ENTERING && status !== ENTERED) { nextStatus = ENTERING } } else { if (status === ENTERING || status === ENTERED) { nextStatus = EXITING } } } this.updateStatus(false, nextStatus) } updateStatus(mounting = false, nextStatus) { if (nextStatus !== null) { // nextStatus will always be ENTERING or EXITING. this.cancelNextCallback() if (nextStatus === ENTERING) { this.performEnter(mounting) } else { this.performExit() } } else if (this.props.unmountOnExit && this.state.status === EXITED) { this.setState({ status: UNMOUNTED }) } } performEnter(mounting) { const { enter } = this.props const appearing = this.context ? this.context.isMounting : mounting const [maybeNode, maybeAppearing] = this.props.nodeRef ? [appearing] : [ReactDOM.findDOMNode(this), appearing] const timeouts = this.getTimeouts() const enterTimeout = appearing ? timeouts.appear : timeouts.enter // no enter animation skip right to ENTERED // if we are mounting and running this it means appear _must_ be set if ((!mounting && !enter) || config.disabled) { this.safeSetState({ status: ENTERED }, () => { this.props.onEntered(maybeNode) }) return } this.props.onEnter(maybeNode, maybeAppearing) this.safeSetState({ status: ENTERING }, () => { this.props.onEntering(maybeNode, maybeAppearing) this.onTransitionEnd(enterTimeout, () => { this.safeSetState({ status: ENTERED }, () => { this.props.onEntered(maybeNode, maybeAppearing) }) }) }) } performExit() { const { exit } = this.props const timeouts = this.getTimeouts() const maybeNode = this.props.nodeRef ? undefined : ReactDOM.findDOMNode(this) // no exit animation skip right to EXITED if (!exit || config.disabled) { this.safeSetState({ status: EXITED }, () => { this.props.onExited(maybeNode) }) return } this.props.onExit(maybeNode) this.safeSetState({ status: EXITING }, () => { this.props.onExiting(maybeNode) this.onTransitionEnd(timeouts.exit, () => { this.safeSetState({ status: EXITED }, () => { this.props.onExited(maybeNode) }) }) }) } cancelNextCallback() { if (this.nextCallback !== null) { this.nextCallback.cancel() this.nextCallback = null } } safeSetState(nextState, callback) { // This shouldn't be necessary, but there are weird race conditions with // setState callbacks and unmounting in testing, so always make sure that // we can cancel any pending setState callbacks after we unmount. callback = this.setNextCallback(callback) this.setState(nextState, callback) } setNextCallback(callback) { let active = true this.nextCallback = event => { if (active) { active = false this.nextCallback = null callback(event) } } this.nextCallback.cancel = () => { active = false } return this.nextCallback } // 监听过渡end onTransitionEnd(timeout, handler) { this.setNextCallback(handler) const node = this.props.nodeRef ? this.props.nodeRef.current : ReactDOM.findDOMNode(this) const doesNotHaveTimeoutOrListener = timeout == null && !this.props.addEndListener if (!node || doesNotHaveTimeoutOrListener) { setTimeout(this.nextCallback, 0) return } if (this.props.addEndListener) { const [maybeNode, maybeNextCallback] = this.props.nodeRef ? [this.nextCallback] : [node, this.nextCallback] this.props.addEndListener(maybeNode, maybeNextCallback) } if (timeout != null) { setTimeout(this.nextCallback, timeout) } } render() { const status = this.state.status if (status === UNMOUNTED) { return null } const { children, // filter props for `Transition` in: _in, mountOnEnter: _mountOnEnter, unmountOnExit: _unmountOnExit, appear: _appear, enter: _enter, exit: _exit, timeout: _timeout, addEndListener: _addEndListener, onEnter: _onEnter, onEntering: _onEntering, onEntered: _onEntered, onExit: _onExit, onExiting: _onExiting, onExited: _onExited, nodeRef: _nodeRef, ...childProps } = this.props return ( // allows for nested Transitions <TransitionGroupContext.Provider value={null}> {typeof children === 'function' ? children(status, childProps) : React.cloneElement(React.Children.only(children), childProps)} </TransitionGroupContext.Provider> ) } }可以看到,和Vue是非常相似的,只不过这里变成了在React的各个生命周期函数了进行处理。
到了这里,我们会发现不管是Vue的transiton组件,还是React这个transiton-group组件,着重处理的都是css属性的动画。
数据驱动的动画
而实际场景中总是会遇到css无法处理的动画,这个时候,可以有两种解决方案:
通过ref获取dom,然后采用我们传统的js方案。
通过state状态维护绘制dom的数据,不断通过setState更新state类驱动视图自动刷新以上就是前端如何实现动画过渡效果的详细内容,更多关于前端实现动画过渡效果的资料请关注其它相关文章!
标签:前端,动画,前端,动画过渡
圆月山庄资源网 Design By www.vgjia.com
免责声明:本站文章均来自网站采集或用户投稿,网站不提供任何软件下载或自行开发的软件! 如有用户或公司发现本站内容信息存在侵权行为,请邮件告知! 858582#qq.com
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 小骆驼-《草原狼2(蓝光CD)》[原抓WAV+CUE]
- 群星《欢迎来到我身边 电影原声专辑》[320K/MP3][105.02MB]
- 群星《欢迎来到我身边 电影原声专辑》[FLAC/分轨][480.9MB]
- 雷婷《梦里蓝天HQⅡ》 2023头版限量编号低速原抓[WAV+CUE][463M]
- 群星《2024好听新歌42》AI调整音效【WAV分轨】
- 王思雨-《思念陪着鸿雁飞》WAV
- 王思雨《喜马拉雅HQ》头版限量编号[WAV+CUE]
- 李健《无时无刻》[WAV+CUE][590M]
- 陈奕迅《酝酿》[WAV分轨][502M]
- 卓依婷《化蝶》2CD[WAV+CUE][1.1G]
- 群星《吉他王(黑胶CD)》[WAV+CUE]
- 齐秦《穿乐(穿越)》[WAV+CUE]
- 发烧珍品《数位CD音响测试-动向效果(九)》【WAV+CUE】
- 邝美云《邝美云精装歌集》[DSF][1.6G]
- 吕方《爱一回伤一回》[WAV+CUE][454M]