From 50ee7232f3c98fe24a1f2e31dba9508cfec14887 Mon Sep 17 00:00:00 2001 From: gjj <Ganjj@probim.com.cn> Date: Fri, 21 Feb 2025 17:35:39 +0800 Subject: [PATCH] 模型预览 --- src/components/TransitionSize.vue | 253 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 253 insertions(+), 0 deletions(-) diff --git a/src/components/TransitionSize.vue b/src/components/TransitionSize.vue new file mode 100644 index 0000000..5f76e7d --- /dev/null +++ b/src/components/TransitionSize.vue @@ -0,0 +1,253 @@ +<template> + <transition + :name="name" + @before-appear="beforeAppear" + @appear="appear" + @after-appear="afterAppear" + @appear-cancelled="appearCancelled" + @before-enter="beforeEnter" + @enter="enter" + @after-enter="afterEnter" + @enter-cancelled="enterCancelled" + @before-leave="beforeLeave" + @leave="leave" + @after-leave="afterLeave" + @leave-cancelled="leaveCancelled" + > + <slot></slot> + </transition> +</template> + +<script> + export default { + name: "CollapseTransition", + props: { + name: { + type: String, + required: false, + default: "collapse", + }, + dimension: { + type: String, + required: false, + default: "height", + validator: (value) => { + return ["height", "width"].includes(value); + }, + }, + duration: { + type: Number, + required: false, + default: 300, + }, + easing: { + type: String, + required: false, + default: "ease-in-out", + }, + }, + data() { + return { + cachedStyles: null, + }; + }, + computed: { + transition() { + let transitions = []; + Object.keys(this.cachedStyles).forEach((key) => { + transitions.push(`${this.convertToCssProperty(key)} ${this.duration}ms ${this.easing}`); + }); + return transitions.join(", "); + }, + }, + watch: { + dimension() { + this.clearCachedDimensions(); + }, + }, + methods: { + beforeAppear(el) { + // Emit the event to the parent + this.$emit("before-appear", el); + }, + appear(el) { + // Emit the event to the parent + this.$emit("appear", el); + }, + afterAppear(el) { + // Emit the event to the parent + this.$emit("after-appear", el); + }, + appearCancelled(el) { + // Emit the event to the parent + this.$emit("appear-cancelled", el); + }, + beforeEnter(el) { + // Emit the event to the parent + this.$emit("before-enter", el); + }, + enter(el, done) { + // Because width and height may be 'auto', + // first detect and cache the dimensions + this.detectAndCacheDimensions(el); + // The order of applying styles is important: + // - 1. Set styles for state before transition + // - 2. Force repaint + // - 3. Add transition style + // - 4. Set styles for state after transition + // If the order is not right and you open any 2nd level submenu + // for the first time, the transition will not work. + this.setClosedDimensions(el); + this.hideOverflow(el); + this.forceRepaint(el); + this.setTransition(el); + this.setOpenedDimensions(el); + // Emit the event to the parent + this.$emit("enter", el, done); + // Call done() when the transition ends + // to trigger the @after-enter event. + setTimeout(done, this.duration); + }, + afterEnter(el) { + // Clean up inline styles + this.unsetOverflow(el); + this.unsetTransition(el); + this.unsetDimensions(el); + this.clearCachedDimensions(); + // Emit the event to the parent + this.$emit("after-enter", el); + }, + enterCancelled(el) { + // Emit the event to the parent + this.$emit("enter-cancelled", el); + }, + beforeLeave(el) { + // Emit the event to the parent + this.$emit("before-leave", el); + }, + leave(el, done) { + // For some reason, @leave triggered when starting + // from open state on page load. So for safety, + // check if the dimensions have been cached. + this.detectAndCacheDimensions(el); + // The order of applying styles is less important + // than in the enter phase, as long as we repaint + // before setting the closed dimensions. + // But it is probably best to use the same + // order as the enter phase. + this.setOpenedDimensions(el); + this.hideOverflow(el); + this.forceRepaint(el); + this.setTransition(el); + this.setClosedDimensions(el); + // Emit the event to the parent + this.$emit("leave", el, done); + // Call done() when the transition ends + // to trigger the @after-leave event. + // This will also cause v-show + // to reapply 'display: none'. + setTimeout(done, this.duration); + }, + afterLeave(el) { + // Clean up inline styles + this.unsetOverflow(el); + this.unsetTransition(el); + this.unsetDimensions(el); + this.clearCachedDimensions(); + // Emit the event to the parent + this.$emit("after-leave", el); + }, + leaveCancelled(el) { + // Emit the event to the parent + this.$emit("leave-cancelled", el); + }, + detectAndCacheDimensions(el) { + // Cache actual dimensions + // only once to void invalid values when + // triggering during a transition + if (this.cachedStyles) return; + const visibility = el.style.visibility; + const display = el.style.display; + // Trick to get the width and + // height of a hidden element + el.style.visibility = "hidden"; + el.style.display = ""; + this.cachedStyles = this.detectRelevantDimensions(el); + // Restore any original styling + el.style.visibility = visibility; + el.style.display = display; + }, + clearCachedDimensions() { + this.cachedStyles = null; + }, + detectRelevantDimensions(el) { + // These properties will be transitioned + if (this.dimension === "height") { + return { + height: el.offsetHeight + "px", + paddingTop: el.style.paddingTop || this.getCssValue(el, "padding-top"), + paddingBottom: el.style.paddingBottom || this.getCssValue(el, "padding-bottom"), + }; + } + if (this.dimension === "width") { + return { + width: el.offsetWidth + "px", + paddingLeft: el.style.paddingLeft || this.getCssValue(el, "padding-left"), + paddingRight: el.style.paddingRight || this.getCssValue(el, "padding-right"), + }; + } + return {}; + }, + setTransition(el) { + el.style.transition = this.transition; + }, + unsetTransition(el) { + el.style.transition = ""; + }, + hideOverflow(el) { + el.style.overflow = "hidden"; + }, + unsetOverflow(el) { + el.style.overflow = ""; + }, + setClosedDimensions(el) { + Object.keys(this.cachedStyles).forEach((key) => { + el.style[key] = "0"; + }); + }, + setOpenedDimensions(el) { + Object.keys(this.cachedStyles).forEach((key) => { + el.style[key] = this.cachedStyles[key]; + }); + }, + unsetDimensions(el) { + Object.keys(this.cachedStyles).forEach((key) => { + el.style[key] = ""; + }); + }, + forceRepaint(el) { + // Force repaint to make sure the animation is triggered correctly. + // Thanks: https://markus.oberlehner.net/blog/transition-to-height-auto-with-vue/ + getComputedStyle(el)[this.dimension]; + }, + getCssValue(el, style) { + return getComputedStyle(el, null).getPropertyValue(style); + }, + convertToCssProperty(style) { + // Example: convert 'paddingTop' to 'padding-top' + // Thanks: https://gist.github.com/tan-yuki/3450323 + const upperChars = style.match(/([A-Z])/g); + if (!upperChars) { + return style; + } + for (let i = 0, n = upperChars.length; i < n; i++) { + style = style.replace(new RegExp(upperChars[i]), "-" + upperChars[i].toLowerCase()); + } + if (style.slice(0, 1) === "-") { + style = style.slice(1); + } + return style; + }, + }, + }; +</script> -- Gitblit v1.9.3