Appearance
Transition
使用
<script type="module">
import {
Transition,
Teleport,
defineAsyncComponent,
createRenderer,
h,
render,
Text,
Fragment,
ref,
reactive,
getCurrentInstance,
onMounted,
provide,
inject,
toRef,
KeepAlive,
} from "/node_modules/@vue/runtime-dom/dist/runtime-dom.esm-browser.js";
const props = {
onBeforeEnter(el) {
console.log(el, "beforeEnter");
},
onEnter(el) {
console.log(el, "enter");
},
onLeave(el) {
console.log(el, "leave");
},
};
render(
h(Transition, props, {
default: () => {
return h(
"div",
{ style: { width: "100px", height: "100px", background: "red" } },
"haha"
);
},
}),
app
);
setTimeout(() => {
render(
h(Transition, props, {
default: () => {
return h(
"p",
{ style: { width: "100px", height: "100px", background: "blue" } },
"world"
);
},
}),
app
);
}, 4000);
setTimeout(() => {
render(
h(Transition, props, {
default: () => {
return h(
"div",
{ style: { width: "100px", height: "100px", background: "red" } },
"haha"
);
},
}),
app
);
}, 8000);
</script>
<style>
.v-enter-active,
.v-leave-active {
transition: opacity 2s ease;
}
.v-enter-from,
.v-leave-to {
opacity: 0;
}
</style>
1.transition 核心实现
import { isKeepAlive } from "./keepAlive";
import { h } from "./h";
function nextFrame(cb) {
requestAnimationFrame(() => {
requestAnimationFrame(cb);
});
}
function resolveTransitionProps(rawProps) {
const {
name = "v",
enterFromClass = `${name}-enter-from`,
enterActiveClass = `${name}-enter-active`,
enterToClass = `${name}-enter-to`,
leaveFromClass = `${name}-leave-from`,
leaveActiveClass = `${name}-leave-active`,
leaveToClass = `${name}-leave-to`,
onBeforeEnter, // 进入前
onEnter, // 进入
onLeave, // 离开时
} = rawProps;
return {
onBeforeEnter(el) {
onBeforeEnter && onBeforeEnter(el);
el.classList.add(enterFromClass); // 进入前添加的类名
el.classList.add(enterActiveClass);
},
onEnter(el, done) {
const resolve = () => {
// 进入完毕后全部移除
el.classList.remove(enterActiveClass);
el.classList.remove(enterToClass);
done && done();
};
onEnter && onEnter(el, resolve);
nextFrame(() => {
el.classList.remove(enterFromClass);
el.classList.add(enterToClass); // 进入后添加类名
// 绑定transition组件
// 用户没传递onEnter 或者onEnter参数只有一个
if (!onEnter || onEnter.length <= 1) {
// 监听transitionend事件
el.addEventListener("transitionend", resolve);
}
});
},
onLeave(el, done) {
const resolve = () => {
// 进入完毕后全部移除
el.classList.remove(leaveActiveClass);
el.classList.remove(leaveToClass);
done && done();
};
el.classList.add(leaveFromClass); // 离开
document.body.offsetHeight; // 让leaveFromClass 立即影响变化
el.classList.add(leaveActiveClass);
nextFrame(() => {
el.classList.remove(leaveFromClass);
el.classList.add(leaveToClass); // 离开
if (!onLeave || onLeave.length <= 1) {
// 监听transitionend事件
el.addEventListener("transitionend", resolve);
}
});
onLeave && onLeave(el, resolve); // 调用leave
},
};
}
export const Transition = (props, { slots }) => {
return h(BaseTransitionImpl, resolveTransitionProps(props), slots);
};
1.Transition 组件
function getKeepAliveChild(vnode) {
// 是keep-alive 则拿keep-alive 插槽中的内容
return isKeepAlive(vnode)
? vnode.children
? vnode.children.default()
: undefined
: vnode;
}
function resolveTransitionHooks(props) {
let { onBeforeEnter, onEnter, onLeave } = props;
const hooks = {
beforeEnter(el) {
onBeforeEnter && onBeforeEnter(el);
},
enter(el) {
onEnter && onEnter(el);
},
leave(el, remove) {
onLeave && onLeave(el, remove);
},
};
return hooks;
}
export const BaseTransitionImpl = {
name: "BaseTransition",
props: { onBeforeEnter: Function, onEnter: Function, onLeave: Function },
setup(props, { slots }) {
const instance = getCurrentInstance();
return () => {
const child = slots.default && slots.default();
// 如果没有插槽则直接结束即可
if (!child) {
return;
}
// 获取内部插槽内容
const innerChild = getKeepAliveChild(child);
const enterHooks = resolveTransitionHooks(props);
innerChild.transition = enterHooks;
const oldChild = instance.subTree;
const oldInnerChild = oldChild && getKeepAliveChild(oldChild);
if (oldInnerChild) {
// 意味着离开
if (!isSameVNode(innerChild, oldChild)) {
const leavingHooks = resolveTransitionHooks(props);
oldInnerChild.transition = leavingHooks;
}
}
return child;
};
},
};
2.挂载元素
const mountElement = (vnode, container, anchor, parentComponent) => {
const { type, props, children, shapeFlag, transition } = vnode;
const el = (vnode.el = hostCreateElement(type));
if (props) {
for (let key in props) {
hostPatchProp(el, key, null, props[key]);
}
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, el);
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, children);
}
// 插入前
if (transition) {
transition.beforeEnter(el);
}
// 插入
hostInsert(el, container, anchor);
// 插入后
if (transition) {
transition.enter(el);
}
};
const remove = (vnode) => {
const { type, el, transition } = vnode;
const performRemove = () => {
hostRemove(el);
};
if (transition) {
// 触发离开后,在调用真实的删除操作
transition.leave(el, performRemove);
} else {
performRemove();
}
};
const unmount = (vnode, parentComponent) => {
// ...
remove(vnode);
};
import { isKeepAlive } from "./keepAlive";
import { h } from "./h";
import { isSameVNode } from "./vnode";
import { getCurrentInstance } from "./component";
function nextFrame(cb) {
requestAnimationFrame(() => {
requestAnimationFrame(cb);
});
}
function resolveTransitionProps(rawProps) {
const {
name = "v",
enterFromClass = `${name}-enter-from`,
enterActiveClass = `${name}-enter-active`,
enterToClass = `${name}-enter-to`,
leaveFromClass = `${name}-leave-from`,
leaveActiveClass = `${name}-leave-active`,
leaveToClass = `${name}-leave-to`,
onBeforeEnter, // 进入前
onEnter, // 进入
onLeave, // 离开时
} = rawProps;
return {
onBeforeEnter(el) {
onBeforeEnter && onBeforeEnter(el);
el.classList.add(enterFromClass); // 进入前添加的类名
el.classList.add(enterActiveClass);
},
onEnter(el) {
const resolve = () => {
// 进入完毕后全部移除
el.classList.remove(enterActiveClass);
el.classList.remove(enterToClass);
};
onEnter && onEnter(el, resolve);
nextFrame(() => {
el.classList.remove(enterFromClass);
el.classList.add(enterToClass); // 进入后添加类名
// 绑定transition组件
// 用户没传递onEnter 或者onEnter参数只有一个
if (!onEnter || onEnter.length <= 1) {
// 监听transitionend事件
el.addEventListener("transitionend", resolve);
}
});
},
onLeave(el, done) {
const resolve = () => {
// 进入完毕后全部移除
el.classList.remove(leaveActiveClass);
el.classList.remove(leaveToClass);
done && done();
};
el.classList.add(leaveFromClass); // 离开
document.body.offsetHeight; // 让leaveFromClass 立即影响变化
el.classList.add(leaveActiveClass);
nextFrame(() => {
el.classList.remove(leaveFromClass);
el.classList.add(leaveToClass); // 离开
if (!onLeave || onLeave.length <= 1) {
// 监听transitionend事件
el.addEventListener("transitionend", resolve);
}
});
onLeave && onLeave(el, resolve); // 调用leave
},
};
}
export const BaseTransitionImpl = {
name: "BaseTransition",
props: ["onBeforeEnter", "onEnter", "onLeave"],
setup(props, { slots }) {
const instance = getCurrentInstance();
return () => {
const child = slots.default && slots.default();
// 如果没有插槽则直接结束即可
if (!child) {
return;
}
// 获取内部插槽内容
const innerChild = getKeepAliveChild(child);
const enterHooks = resolveTransitionHooks(props);
innerChild.transition = enterHooks;
const oldChild = instance.subTree;
const oldInnerChild = oldChild && getKeepAliveChild(oldChild);
if (oldInnerChild) {
// 意味着离开
if (!isSameVNode(innerChild, oldChild)) {
const leavingHooks = resolveTransitionHooks(props);
oldInnerChild.transition = leavingHooks;
}
}
return child;
};
},
};
export const Transition = (props, { slots }) => {
return h(BaseTransitionImpl, resolveTransitionProps(props), slots);
};
function resolveTransitionHooks(props) {
let { onBeforeEnter, onEnter, onLeave } = props;
const hooks = {
beforeEnter(el) {
onBeforeEnter && onBeforeEnter(el);
},
enter(el) {
onEnter && onEnter(el);
},
leave(el, remove) {
onLeave && onLeave(el, remove);
},
};
return hooks;
}
function getKeepAliveChild(vnode) {
// 是keep-alive 则拿keep-alive 插槽中的内容
return isKeepAlive(vnode)
? vnode.children
? vnode.children.default()
: undefined
: vnode;
}