简介
动画这个概念非常宽泛,涉及各个领域,这里我们把范围缩小到前端网页应用层面上,不用讲游戏领域的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类驱动视图自动刷新以上就是前端如何实现动画过渡效果的详细内容,更多关于前端实现动画过渡效果的资料请关注其它相关文章!
标签:前端,动画,前端,动画过渡
P70系列延期,华为新旗舰将在下月发布
3月20日消息,近期博主@数码闲聊站 透露,原定三月份发布的华为新旗舰P70系列延期发布,预计4月份上市。
而博主@定焦数码 爆料,华为的P70系列在定位上已经超过了Mate60,成为了重要的旗舰系列之一。它肩负着重返影像领域顶尖的使命。那么这次P70会带来哪些令人惊艳的创新呢?
根据目前爆料的消息来看,华为P70系列将推出三个版本,其中P70和P70 Pro采用了三角形的摄像头模组设计,而P70 Art则采用了与上一代P60 Art相似的不规则形状设计。这样的外观是否好看见仁见智,但辨识度绝对拉满。
更新动态
- 小骆驼-《草原狼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]