How to realize animation transition effect in front end
- 2021-10-27 06:09:37
- OfStack
Traditional transition animation
css Transition Animation
js animation
Comparison between traditional and Vue/React frameworks
Transition Animation in Vue Frame
transition component
modules/transition
Transition Animation in React
Data-driven animation
Brief introduction
The concept of animation is very broad, involving various fields. Here, we narrow the scope to the front-end web application level, without talking about Animate in the game field.
At present, most web applications are developed based on frameworks, such as Vue, React, etc. They are all based on data-driven views, so let's compare 1, how do we realize animation or transition effects without these frameworks, and then how do we realize them using data-driven.
Traditional transition animation
Animation effect is very important for experience, but it may be a very weak link for many developers. After the advent of css3, the most commonly used animation transition for many beginners may be the ability of css3.
css Transition Animation
css start transition animation is very simple, write transition attribute is OK, write 1 demo below
<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;
}
The effect is still very good. transition of css3 basically meets most animation requirements. If not, there is a real css3 animation.
animate-css
The famous css animation library, who knows who uses it.
Whether it is css3 transition or css3 animation, we simply use it by switching the class name of class. If we want to do callback processing, the browser also provides animation frame events such as ontransitionend and onanimationend, which can be monitored through js interface.
var el = document.querySelector('#app')
el.addEventListener('transitionstart', () => {
console.log('transition start')
})
el.addEventListener('transitionend', () => {
console.log('transition end')
})
ok, this is the basis of css animation. Most animation transition requirements can also be realized through js encapsulation, but the limitation lies in that it can only control the attribute animation supported by css, and the control power is still slightly weaker.
js Animation
After all, js is a custom coding program, which has strong control over animation and can achieve various effects that css does not support. So what is the basis of js animation?
Simply put, the so-called animation is to constantly update the attributes of an element on the timeline, and then give it to the browser for redrawing, which becomes an animation visually. Cut the crap and have a chestnut first:
<div id="app" class="normal"></div>
// Tween It's just a slow-moving function
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(' End of animation and start over ')
time = 0;
}
setTimeout(() => {
startSport()
}, fps)
}
startSport()
Constantly updating attributes on the timeline can be achieved through setTimeout or requestAnimation. As for Tween slow-moving function, it is similar to the concept of interpolation. Given a series of variables, you can get the value at any time in the interval. Pure mathematical formula, almost all animation frameworks will be used. If you want to know, you can refer to Zhang Xinxu's Tween.js
OK, this minimalist demo is also the core foundation of js animation. We can see that we perfectly control the generation process of transition values through the program, and all other complex animation mechanisms are in this mode.
Comparison of traditional and Vue/React frameworks
From the previous example, whether it is css transition or js transition, we directly get dom element, and then perform attribute operation on dom element.
Vue/React both introduce the concept of virtual dom. Data-driven view, we try not to operate dom, but only control data, so how can we drive animation at the data level?
Transition Animation in the Framework of Vue
You can look at the document once first
Vue Transition Animation
Let's not talk about how to use it. Let's analyze how the transition component provided by Vue realizes animation transition support.
transition Component
First look at the transition component code, path "src/platforms/web/runtime/components/transition. js"
The core code is as follows:
// Auxiliary function, copy props Data of
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: ?Object = options._parentListeners
for (const key in listeners) {
data[camelize(key)] = listeners[key]
}
return data
}
export default {
name: 'transition',
props: transitionProps,
abstract: true, // Abstract component, which means that it will not be rendered as a real dom Assisted development
render (h: Function) {
// Pass slots Get the real rendering element children
let children: any = this.$slots.default
const mode: string = this.mode
const rawChild: VNode = children[0]
// Add only 1key
// component instance. This key will be used to remove pending leaving nodes
// during entering.
const id: string = `__transition-${this._uid}-`
child.key = getKey(id)
: child.key
// data Upward injection transition Property, save the props Transmitted data
const data: Object = (child.data || (child.data = {})).transition = extractTransitionData(this)
const oldRawChild: VNode = this._vnode
const oldChild: VNode = getRealChild(oldRawChild)
// important for dynamic transitions!
const oldData: Object = oldChild.data.transition = extend({}, data)
// handle transition mode
if (mode === 'out-in') {
// return placeholder node and queue update when leave finishes
this._leaving = true
mergeVNodeHook(oldData, 'afterLeave', () => {
this._leaving = false
this.$forceUpdate()
})
return placeholder(h, rawChild)
} else if (mode === 'in-out') {
let delayedLeave
const performLeave = () => { delayedLeave() }
mergeVNodeHook(data, 'afterEnter', performLeave)
mergeVNodeHook(data, 'enterCancelled', performLeave)
mergeVNodeHook(oldData, 'delayLeave', leave => { delayedLeave = leave })
}
return rawChild
}
}
It can be seen that the function of this component itself is relatively simple, that is, the element children to be rendered is obtained through slots, and then the props attribute data copy of transition is applied to the transtion attribute of data for subsequent injection into the life cycle, and mergeVNodeHook is for life cycle management.
modules/transition
Then look down at the life cycle correlation, path:
src/platforms/web/runtime/modules/transition.js
Look at the default export first:
function _enter (_: any, vnode: VNodeWithData) {
if (vnode.data.show !== true) {
enter(vnode)
}
}
export default inBrowser ? {
create: _enter,
activate: _enter,
remove (vnode: VNode, rm: Function) {
if (vnode.data.show !== true) {
leave(vnode, rm)
}
}
} : {}
Here, inBrowser is treated as true, because we are analyzing the browser environment.
Then look at the enter and leave functions, and first look at enter:
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: ?() => void) {
const el: any = vnode.elm
// call leave callback now
if (isDef(el._leaveCb)) {
el._leaveCb.cancelled = true
el._leaveCb()
}
// Upper 1 Step injection data Adj. transition Data
const data = resolveTransition(vnode.data.transition)
if (isUndef(data)) {
return
}
/* istanbul ignore if */
if (isDef(el._enterCb) || el.nodeType !== 1) {
return
}
const {
css,
type,
enterClass,
enterToClass,
enterActiveClass,
appearClass,
appearToClass,
appearActiveClass,
beforeEnter,
enter,
afterEnter,
enterCancelled,
beforeAppear,
appear,
afterAppear,
appearCancelled,
duration
} = data
let context = activeInstance
let transitionNode = activeInstance.$vnode
const isAppear = !context._isMounted || !vnode.isRootInsert
if (isAppear && !appear && appear !== '') {
return
}
// To get the right time, it should be injected className
const startClass = isAppear && appearClass
? appearClass
: enterClass
const activeClass = isAppear && appearActiveClass
? appearActiveClass
: enterActiveClass
const toClass = isAppear && appearToClass
? appearToClass
: enterToClass
const beforeEnterHook = isAppear
? (beforeAppear || beforeEnter)
: beforeEnter
const enterHook = isAppear
? (typeof appear === 'function' ? appear : enter)
: enter
const afterEnterHook = isAppear
? (afterAppear || afterEnter)
: afterEnter
const enterCancelledHook = isAppear
? (appearCancelled || enterCancelled)
: enterCancelled
const explicitEnterDuration: any = toNumber(
isObject(duration)
? duration.enter
: duration
)
const expectsCSS = css !== false && !isIE9
const userWantsControl = getHookArgumentsLength(enterHook)
// Callback processing after the end of the transition, deleting the class
const cb = el._enterCb = once(() => {
if (expectsCSS) {
removeTransitionClass(el, toClass)
removeTransitionClass(el, activeClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass)
}
enterCancelledHook && enterCancelledHook(el)
} else {
afterEnterHook && afterEnterHook(el)
}
el._enterCb = null
})
// dom When entering, add start class Make a transition
beforeEnterHook && beforeEnterHook(el)
if (expectsCSS) {
// Set the default style before the transition starts
addTransitionClass(el, startClass)
addTransitionClass(el, activeClass)
// Under browser rendering 1 Frame Delete the default style and add toClass
// Add end Event listening, callback is the above cb
nextFrame(() => {
removeTransitionClass(el, startClass)
if (!cb.cancelled) {
addTransitionClass(el, toClass)
if (!userWantsControl) {
if (isValidDuration(explicitEnterDuration)) {
setTimeout(cb, explicitEnterDuration)
} else {
whenTransitionEnds(el, type, cb)
}
}
}
})
}
if (vnode.data.show) {
toggleDisplay && toggleDisplay()
enterHook && enterHook(el, cb)
}
if (!expectsCSS && !userWantsControl) {
cb()
}
}
enter uses a function whenTransitionEnds, which is actually listening for transition or animation end events:
export let transitionEndEvent = 'transitionend'
export let animationEndEvent = 'animationend'
export function whenTransitionEnds (
el: Element,
expectedType: ?string,
cb: Function
) {
const { type, timeout, propCount } = getTransitionInfo(el, expectedType)
if (!type) return cb()
const event: string = type === TRANSITION ? transitionEndEvent : animationEndEvent
let ended = 0
const end = () => {
el.removeEventListener(event, onEnd)
cb()
}
const onEnd = e => {
if (e.target === el) {
if (++ended >= propCount) {
end()
}
}
}
setTimeout(() => {
if (ended < propCount) {
end()
}
}, timeout + 1)
el.addEventListener(event, onEnd)
}
OK, at this point, according to the annotation analysis of the above source code, we can find:
Vue first encapsulates a series of auxiliary methods such as addClass/removeClass for operating dom and className. Then, immediately after the lifecycle enterHook, the default initial style of startClass, that is, enterClass, and activeClass are set Then, in the next frame of browser nextFrame, startClass is removed, toClass is added, and end event monitoring processing of transition animation is added After listening for the end event, move cb and remove toClass and activeClassThe process of leave is the same as that of enter, except that className is added and removed in reverse
Conclusion: Vue is essentially the same as the traditional dom, but it is processed in every life cycle of Vue, and it is processed at the time of adding and deleting dom in essence
Transition Animation in React
Oh, we turned over the React document, and we didn't find any transition animation processing. Hey, it seems that the official support is not original.
But we can do it ourselves, for example, maintain a state through useState, and switch className according to the state in render, but what should we do if it is complicated?
Fortunately, I found a wheel plug-in react-transition-group in the community
Well, post the source code directly. With the previous analysis of Vue, this is very easy to understand, but it is simpler:
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
}
// Initial dom Update the default initial state when
componentDidMount() {
this.updateStatus(true, this.appearStatus)
}
// data When updating, update the corresponding status
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
}
// Monitor transition 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>
)
}
}
It can be seen that it is very similar to Vue, except that it is processed in various life cycle functions of React.
At this point, we will find that both the transiton component of Vue and the transiton-group component of React focus on the animation of css attributes.
Data-driven animation
In actual scenes, there will always be animations that css can't handle. At this time, there are two solutions:
Obtain dom through ref, and then adopt our traditional js scheme.
The data of dom is drawn by state state maintenance, and the state class is continuously updated by setState to drive the view to refresh automatically
The above is the front-end how to achieve animation transition effect details, more information about the front-end animation transition effect please pay attention to other related articles on this site!