전면에서 어떻게 애니메이션 전환 효과를 실현합니까
소개
애니메이션이라는 개념은 매우 광범위하고 각 분야와 관련된다. 여기서 우리는 범위를 전방 웹 응용 차원으로 축소했다. 게임 분야의 애니메이트는 말할 것도 없고 모든 것은 가장 간단한 것부터 시작한다.
현재 대부분의 웹 응용 프로그램은 프레임워크를 바탕으로 개발된 것이다. 예를 들어 Vue,React 등은 모두 데이터 구동 보기를 바탕으로 한다. 그러면 이런 프레임워크가 없을 때 우리가 어떻게 애니메이션이나 과도 효과를 실현하고 데이터 구동을 사용하면 어떻게 실현하는지 비교해 보자.
기존 변환 애니메이션
애니메이션 효과는 체험에 매우 중요한 효과가 있지만 많은 개발자들에게 매우 약한 부분일 수 있다.css3가 등장한 후에 많은 초보자들이 가장 자주 사용하는 애니메이션 과도는 아마도 css3의 능력일 것이다.
css 변환 애니메이션
css 시작 과도 애니메이션은 매우 간단합니다.transition 속성을 쓰면 됩니다. 다음은 데모를 쓰십시오
<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은 대부분의 애니메이션 수요를 기본적으로 만족시켰다. 만약에 만족하지 않으면 진정한 css3animation도 있다.animate-css
명성이 자자한 css 애니메이션 라이브러리, 누가 쓰는지 누가 알겠는가.
css3transition이든 css3animation이든 우리가 간단하게 사용하는 것은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, 이 미니멀 데모도 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: ?Object = options._parentListeners
for (const key in listeners) {
data[camelize(key)] = listeners[key]
}
return data
}
export default {
name: 'transition',
props: transitionProps,
abstract: true, // , dom,
render (h: Function) {
// slots children
let children: any = this.$slots.default
const mode: string = this.mode
const rawChild: VNode = children[0]
// key
// 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 transition , props
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
}
}
이를 통해 알 수 있듯이 이 구성 요소 자체의 기능은 비교적 간단하다. 바로 slots를 통해 렌더링이 필요한 요소인children을 얻은 다음에transition의props 속성 데이터copy를 데이터의transition 속성에 추가하여 후속 주입 생명주기에 사용하도록 하는 것이다.mergeVNodeHook는 생명주기 관리를 하는 것이다.modules/transition
그런 다음 라이프 사이클 관련 경로:
src/platforms/web/runtime/modules/transition.js
기본 내보내기 보기:
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)
}
}
} : {}
여기에서 inBrowser는true로 간주합니다. 왜냐하면 우리가 분석하는 것은 브라우저 환경이기 때문입니다.이어서 enter와leave 함수를 보고 먼저 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()
}
// data transition
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
}
// 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)
// , 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 , start class
beforeEnterHook && beforeEnterHook(el)
if (expectsCSS) {
//
addTransitionClass(el, startClass)
addTransitionClass(el, activeClass)
// , toClass
// end , 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에서 whenTransitionEnds라는 함수를 사용했습니다. 사실은 전환이나 애니메이션이 끝난 이벤트를 감청하는 것입니다.
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, 여기까지 와서 위의 소스 코드에 대한 주석 분석을 통해 알 수 있듯이결론: Vue의 애니메이션 과도 처리 방식은 전통적인dom와 본질적으로 똑같다. 단지 Vue의 각 생명주기에 융합되어 처리되었을 뿐, 본질적으로dom에서 삭제할 시기를 추가하여 처리한다.
React의 변환 애니메이션
아, React 문서를 뒤적였지만 과도 애니메이션 처리가 발견되지 않았습니다.어이, 보아하니 정부의 비원생적인 지지가 있는 것 같다.
그러나 우리는usestate를 통해 하나의 상태를 유지하고 렌더에서 상태에 따라className을 전환할 수 있습니다. 그러나 복잡한 것은 어떻게 해야 합니까?
다행히도 지역 사회에서 바퀴 플러그인을 찾았다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 클래스 구동 보기를 계속 업데이트합니다.
이상은 바로 전단이 어떻게 애니메이션 과도 효과를 실현하는지에 대한 상세한 내용입니다. 전단이 애니메이션 과도 효과를 실현하는 것에 대한 더 많은 자료는 저희 다른 관련 글에 주목하세요!
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
【Vue.js】컴포넌트의 3개의 네비게이션 가드일에서 사용하게 되었기 때문에 1부터 Vue.js에 대해 배웠다. 그 이름에서 알 수 있듯이 무언가를 가드하기위한 처리로, 대체로 페이지 천이 전에 특정 처리를 실행시켜 페이지 천이시키지 않게 한다. Vue.js의 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.