折叠面板

在使用的组件库中没有折叠面板或者不好用时,可以采用以下方法实现自定义样式

  • 第二个JS为一个vue组件,直接用该组件包裹可能显示和隐藏的内容,第一个JS为简单的使用介绍,被包裹的内容在judge控制隐藏显示时会自动展开收起
import collapseC from 'xxx路径'

changeJudge() {
  this.judge = !this.judge
}

<template>
  ...
  <div class="titleStyle" @click="changeJudge">标题-自定义样式</div>
  <collapseC>
    <div v-if="judge">
      需要显示和隐藏的内容
    </div>
  </collapseC>
  ...
</template>
<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 />
  </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() {
      const 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.webkitBackfaceVisibility = 'hidden'
      // el.style.webkitTransitionStyle = 'preserve-3d'
      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.webkitBackfaceVisibility = 'hidden'
      // el.style.webkitTransitionStyle = 'preserve-3d'
      el.style.webkitTransition = this.transition
      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>

posted @ 2022-09-05 21:11  jiazq  阅读(54)  评论(0)    收藏  举报