Skip to content
On this page

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;
}

Released under the MIT License.