Эта статья является четвертой статьей в серии vue, но само собой разумеется, что эта статья является первой статьей."Рассказываем о vue - переходе в деталях"в отдельную большую главу. Однако последняя статья получилась слишком длинной, поэтому пришлось написать ее отдельно. Те, кто заинтересован в предыдущих статьях этой серии, могут щелкнуть ссылку ниже, чтобы отправить
Книга является продолжением предыдущей статьи В предыдущей статье мы в основном представили<transition>пара компонентовpropsа такжеvnode hooksиз输入 => 输出Дизайн процесса, нацеленный на один элементenterтак же какleaveЭффект перехода инкапсулирован в сцену, так что нам нужно только обратить внимание наcssа такжеjsДостаточно бизнес-реализации функции ловушки.
Однако в нашей реальной разработке неизбежно, что многие элементы должны отображаться с использованием эффектов перехода.<transition>Компоненты не удовлетворяют потребности моего бизнеса. В настоящее время,vueупакованный внутри<transition-group>Такие встроенные компоненты для удовлетворения наших потребностей, это помогает нам добиться хорошего списка эффектов переходных эффектов.
1. Приведите пример
Как обычно, перейдем непосредственно к официальному примеру
<template>
<div id="list-demo">
<button v-on:click="add">Add</button>
<button v-on:click="remove">Remove</button>
<transition-group name="list" tag="p">
<span v-for="item in items" v-bind:key="item" class="list-item">
{{ item }}
</span>
</transition-group>
</div>
</template>
<script>
export default {
name: 'home',
data () {
return {
items: [1, 2, 3, 4, 5, 6, 7, 8, 9],
nextNum: 10
}
},
methods: {
randomIndex: function () {
return Math.floor(Math.random() * this.items.length)
},
add: function () {
this.items.splice(this.randomIndex(), 0, this.nextNum++)
},
remove: function () {
this.items.splice(this.randomIndex(), 1)
}
}
}
</script>
<style lang="scss">
.list-item {
display: inline-block;
margin-right: 10px;
}
.list-enter-active, .list-leave-active {
transition: all 1s;
}
.list-enter, .list-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>
Эффект следующий
Далее я возьму вас исследовать вместе<transition-group>дизайн компонентов
Во-вторых, реализация переходной группы
а также<transition>компоненты по сравнению с<transition>Является абстрактным компонентом и работает только с одним элементом. а также<transition-group>Компонент реализует переход списка и отображает реальный узел элемента.
Но их концепции дизайна одинаковы, и они также предоставят намpropsи ряд функций хука для нас как输入интерфейс, который осуществляется внутри输入 => 输出преобразование или обработка привязки
export default {
props,
beforeMount () {
// ...
},
render (h: Function) {
// ...
},
updated () {
// ...
},
methods: {
// ...
}
}
1. реквизит и другой импорт
<transition-group>изpropsа также<transition>изpropsПо сути то же самое, только еще одинtagа такжеmoveClassатрибут удаленmodeАтрибуты
// props
import { transitionProps, extractTransitionData } from './transition'
const props = extend({
tag: String,
moveClass: String
}, transitionProps)
delete props.mode
// other import
import { warn, extend } from 'core/util/index'
import { addClass, removeClass } from '../class-util'
import { setActiveInstance } from 'core/instance/lifecycle'
import {
hasTransition,
getTransitionInfo,
transitionEndEvent,
addTransitionClass,
removeTransitionClass
} from '../transition-util'
2. визуализировать
Во-первых, нам нужно определить ряд переменных для облегчения последующих операций.
-
tag: Из общего контекста приведенного выше дизайна мы можем видеть, что<transition-group>нисколькоabstractсвойство, т. е. он будет отображать реальный узел, затем узелtagявляется обязательным, и его значение по умолчанию равноspan. -
map: Создает пустой объект -
prevChildren: используется для хранения последнего дочернего узла -
rawChildren:Получать<transition-group>Обернутые дочерние узлы -
children: используется для хранения текущего дочернего узла -
transitionData: получить данные рендеринга компонента.
const tag: string = this.tag || this.$vnode.data.tag || 'span'
const map: Object = Object.create(null)
const prevChildren: Array<VNode> = this.prevChildren = this.children
const rawChildren: Array<VNode> = this.$slots.default || []
const children: Array<VNode> = this.children = []
const transitionData: Object = extractTransitionData(this)
Далее следует операция обхода узла, вот привязка анимации перехода для каждого узла в списке
- правильно
rawChildrenпройти и положить каждыйvnodeУзел вынуть; - Если узел существует с__vlistперсонаж
key, тоvnodeбросатьchildrenсередина; - Данные перехода, которые будут извлечены
transitionDataдобавить вvnode.data.transition, чтобы можно было добиться анимации перехода одного элемента в списке
for (let i = 0; i < rawChildren.length; i++) {
const c: VNode = rawChildren[i]
if (c.tag) {
if (c.key != null && String(c.key).indexOf('__vlist') !== 0) {
children.push(c)
map[c.key] = c
;(c.data || (c.data = {})).transition = transitionData
} else if (process.env.NODE_ENV !== 'production') {
const opts: ?VNodeComponentOptions = c.componentOptions
const name: string = opts ? (opts.Ctor.options.name || opts.tag || '') : c.tag
warn(`<transition-group> children must be keyed: <${name}>`)
}
}
}
затем кprevChildrenобрабатывать
- если
prevChildrenсуществует, пройти по нему иtransitionDataназначить наvnode.data.transition, после этого, когдаvnodeдочерний узелenterа такжеleaveКогда на сцене есть анимация перехода, будет выполнена соответствующая анимация перехода. - тогда позвони родному
getBoundingClientRectПолучите информацию о местоположении элемента, запишите ее вvnode.data.posсередина - затем судить
mapсуществует вvnode.key, если он существует, то будетvnodeпомещатьkept, иначе он выбрасывается вremovedв очереди - Наконец, поместите визуализированный элемент в
this.keptсередина,this.removedиспользуется для записи удаленного узла
if (prevChildren) {
const kept: Array<VNode> = []
const removed: Array<VNode> = []
for (let i = 0; i < prevChildren.length; i++) {
const c: VNode = prevChildren[i]
c.data.transition = transitionData
c.data.pos = c.elm.getBoundingClientRect()
if (map[c.key]) {
kept.push(c)
} else {
removed.push(c)
}
}
this.kept = h(tag, null, kept)
this.removed = removed
}
наконец<transition-group>оказывать
return h(tag, null, children)
3. обновление и методы
выше мы ужеrenderСтадия привязана к каждому элементу в спискеtransitionСвязанные эффекты перехода, следующий шаг заключается в том, что при динамическом изменении каждого элемента весь списокupdateВремя для динамичного перехода. Итак, как именно это работает? Далее, давайте посмотрим на логику этого произведения.
I. Требуется ли переход движения
- первый в
updateВ функции ловушки последний дочерний узел будет получен первымprevChildrenа такжеmoveClass; потом судитьchildrenесть иchildrenбудь тоhas move,подобноchildrenне существует илиchildrenнетmoveстатус, то нет необходимости продолжатьupdateизmoveпереход, прямойreturnТолько что
const children: Array<VNode> = this.prevChildren
const moveClass: string = this.moveClass || ((this.name || 'v') + '-move')
if (!children.length || !this.hasMove(children[0].elm, moveClass)) {
return
}
-
hasMove(): Этот метод в основном используется для определенияelЕсть ли в узлеmoveположение дел. - текущий фронт
returnЕсли условия не выполняются, он сначала клонирует DOM-узел, а затем, чтобы избежать CSS-перехода внутри элемента, удалит все клонированные узлы.transitionClasses - Затем повторно добавьте узел клонирования
moveClass, и положить егоdisplayустановить какnone, затем добавьте вthis.$elначальство - Далее через
getTransitionInfoвозьмиtransitionсоответствующую информацию, затем изthis.$elудалить его. На данный момент мы получили, имеет ли узелtransformИнформация
export const hasTransition = inBrowser && !isIE9
hasMove (el: any, moveClass: string): boolean {
// 若不在浏览器中,或者浏览器不支持 transition,直接返回 false 即可
if (!hasTransition) {
return false
}
// 若当前实例上下文的有 _hasMove,直接返回 _hasMove 的值即可
if (this._hasMove) {
return this._hasMove
}
const clone: HTMLElement = el.cloneNode()
if (el._transitionClasses) {
el._transitionClasses.forEach((cls: string) => { removeClass(clone, cls) })
}
addClass(clone, moveClass)
clone.style.display = 'none'
this.$el.appendChild(clone)
const info: Object = getTransitionInfo(clone)
this.$el.removeChild(clone)
return (this._hasMove = info.hasTransform)
}
II. Реализация перехода Move
- Затем на дочерних узлах выполняется волна предварительной обработки.Здесь для обработки дочерних узлов используются три цикла, в основном, чтобы избежать путаницы чтения и записи в DOM в каждом цикле, что помогает предотвратить путаницу в макете.
children.forEach(callPendingCbs)
children.forEach(recordPosition)
children.forEach(applyTranslation)
Обработка трех функций выглядит следующим образом
-
callPendingCbs(): Определить, была ли выполнена анимация перехода предыдущего кадра каждого узла, если нет, выполнить ее заранее_moveCb()а также_enterCb() -
recordPosition(): записать новую позицию каждого узла -
applyTranslation(): Получите старое и новое положение узла соответственно и вычислите разницу.Если есть разница, установите узелtransformАтрибут смещает позицию узла, который необходимо переместить на предыдущую позицию, которая является спискомmoveподготовиться к
function callPendingCbs (c: VNode) {
if (c.elm._moveCb) {
c.elm._moveCb()
}
if (c.elm._enterCb) {
c.elm._enterCb()
}
}
function recordPosition (c: VNode) {
c.data.newPos = c.elm.getBoundingClientRect()
}
function applyTranslation (c: VNode) {
const oldPos = c.data.pos
const newPos = c.data.newPos
const dx = oldPos.left - newPos.left
const dy = oldPos.top - newPos.top
if (dx || dy) {
c.data.moved = true
const s = c.elm.style
s.transform = s.WebkitTransform = `translate(${dx}px,${dy}px)`
s.transitionDuration = '0s'
}
}
- Затем пройдите по дочерним элементам, чтобы достичь
moveпереход. Перед обходом он будет полученdocument.body.offsetHeight, поэтому вычисление происходит, вызывает перекомпоновку и позволяет браузеру перерисовать - затем начните
childrenпоход, во время которого, еслиvnode.data.movedдляtrue, затем выполнитеaddTransitionClassдобавить дочерние узлыmoveClass, и положить егоstyle.transformАтрибут очищен.Поскольку мы уже сместили дочерний узел в предыдущую старую позицию в предварительной обработке дочернего узла, он перейдет из старой позиции в текущую позицию в это время, что мы и хотимmoveэффект перехода - Наконец, узел будет добавлен
transitionendПрослушайте событие в конце перехода и выполните некоторые операции очистки в событии.
this._reflow = document.body.offsetHeight
children.forEach((c: VNode) => {
if (c.data.moved) {
const el: any = c.elm
const s: any = el.style
addTransitionClass(el, moveClass)
s.transform = s.WebkitTransform = s.transitionDuration = ''
el.addEventListener(transitionEndEvent, el._moveCb = function cb (e) {
if (e && e.target !== el) {
return
}
if (!e || /transform$/.test(e.propertyName)) {
el.removeEventListener(transitionEndEvent, cb)
el._moveCb = null
removeTransitionClass(el, moveClass)
}
})
}
})
Примечание. Я кратко опишу условия срабатывания для перекомпоновки браузера, такие как изменение окна браузера, стили расчета, добавление или удаление элементов в DOM, изменение классов элементов и т. д.
- Добавить или удалить видимые элементы DOM
- изменение позиции элемента
- Изменения размера элемента - поля, отступы, границы, ширина и высота
- Изменения содержимого, например, когда пользователь вводит текст в поле ввода, расчетное значение ширины и высоты изменяется из-за изменения размера текста или изображения.
- инициализация рендеринга страницы
- Изменение размера окна браузера - когда происходит событие изменения размера
- Вычислить свойства offsetWidth и offsetHeight
- Установите значение свойства стиля
4. перед монтированием
из-заVDOMв узлеdiffПри обновлении нет гарантии относительного положения удаленного элемента. Так что здесь нужноbeforeMountвнутри функции хукаupdateЛогика рендеринга переписана для достижения желаемого эффекта.
- Сначала получите сам экземпляр
updateметод кэширования - Сверху мы знаем
this.keptЭто последний узел кеша, и количество узлов в нем увеличилось на некотороеtransitionпереходные свойства. Здесь сначала черезsetActiveInstanceКэшировать текущий экземпляр, а затемvnodeпровести__patch__Управляйте и удаляйте то, что нужно удалитьvnode, затем выполнитеrestoreActiveInstanceуказать его экземпляр для восстановления - будет потом
this.keptназначить наthis._vnode, так что это вызывает переход - последний кэшированный
updateвизуализировать узел
beforeMount () {
const update = this._update
this._update = (vnode, hydrating) => {
const restoreActiveInstance = setActiveInstance(this)
// force removing pass
this.__patch__(
this._vnode,
this.kept,
false, // hydrating
true // removeOnly (!important, avoids unnecessary moves)
)
this._vnode = this.kept
restoreActiveInstance()
update.call(this, vnode, hydrating)
}
}
setActiveInstance
export let activeInstance: any = null
export function setActiveInstance(vm: Component) {
const prevActiveInstance = activeInstance
activeInstance = vm
return () => {
activeInstance = prevActiveInstance
}
}
наконец
Статья почти здесь, даtransitionСвязанные встроенные компоненты<transition>так же как<transition-group>Анализ тоже закончился. Различные типы компонентов, один абстрактный компонент и один, который отображает фактический элемент узла, хотят делать одно и то же, инициализируя их для пользователя.输入интерфейс,输入можно получить после输出переходный эффект.
Внешняя коммуникационная группа: 731175396, сердечно приветствую вас, сестра, газета, газета Хань хочет присоединиться