过渡组在 Vue 渲染函数中不起作用

发布于 2025-01-11 19:41:56 字数 10519 浏览 0 评论 0原文

我正在尝试将 Nuxt.js 与 nuxt/tailwind 结合使用来构建我自己的Carousel
我做了一个工作示例,并通过渲染函数重写了模板部分。
但我发现过渡组并不像以前的那样工作。

这是一个简单的版本,transition-group标签在渲染函数中不起作用:

components/Faded.vue

<script>
export default {
  render(h) {
    return h(
      "transition-group",
      {
        props: {
          name: "faded",
        },
      },
      this.$slots.default
    );
  },
};
</script>

<style lang="postcss" scoped>
.faded-leave {
  opacity: 1;
}
.faded-leave-active {
  transition: opacity 1s;
}
.faded-leave-to {
  opacity: 0;
}
.faded-enter {
  opacity: 0;
}
.faded-enter-active {
  transition: opacity 1s;
}
.faded-enter-to {
  opacity: 1;
}
</style>

index.vue

<template>
  <div>
    <button class="bg-green-100" @click="AddIndex">Button</button>
    <Faded>
      <div key="1" v-show="this.showIndex === 0" class="bg-red-100">0</div>
      <div key="2" v-show="this.showIndex === 1" class="bg-yellow-100">1</div>
      <div key="3" v-show="this.showIndex === 2" class="bg-blue-100">2</div>
    </Faded>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showIndex: 0,
    };
  },
  methods: {
    AddIndex() {
      this.showIndex = (this.showIndex + 1) % 3;
    },
  },
};
</script>

原始问题

此代码按我的预期工作:

index.vue

<div class="h-screen flex justify-center">
    <div class="h-[60%] lg:w-[50%] w-full bg-black">
      <Carousel />
    </div>
</div>

Carousel.vue(正确版本)

<template>
    <div class="wrap">
        <transition-group tag="div" class="container" :name="transitionName">
            <Slide v-for="(img,index) of imgs" :key="img.src" v-show="index===show">
              <img :src="img.src" class="object-cover select-none"/>
            </Slide>
        </transition-group>

        <NavigationGroup v-show="showNavigators" :show="show" :length="imgs.length" :setShow="setShow"/>
        
        <button @click="setShow(show-1)" v-show="showArrows && (cycle || show!==0)"  class="navigate-arrow left-2">
          <IconFont type="fas" name="angle-left"/>
        </button>

        <button @click="setShow(show+1)" v-show="showArrows && (cycle || show!==imgs.length-1)"  class="navigate-arrow right-2">
          <IconFont type="fas" name="angle-right"/>
        </button>
    </div>
</template>

<script>
export default {
  props :{
    interval: {type: Number, default: 5000},
    autoplay: {type: Boolean, default: false},
    cycle: {type: Boolean, default: true},
    showArrows: {type: Boolean ,default: true},
    showNavigators: {type: Boolean, default: true},
  },
  data () {
    return {
      timer: undefined,
      transitionName: 'left-in',
      show: 0,
      imgs: [
        { src: require('~/assets/images/bear.jpg') },
        { src: require('~/assets/images/all-namin.png') },
        { src: require('~/assets/images/eat.png') },
        { src: require('~/assets/images/teemo.jpg') },
        { src: require('~/assets/images/gun.png') },
      ]
    }
  },
  mounted () {
    if(this.autoplay && this.cycle){
      this.timer = setInterval(this.nextShow, this.interval)
    }
  },
  methods: {
    setShow (index) {
      this.show = index
    },
    nextShow () {
      this.show++
    }
  },
  watch: {
    show (nVal, oVal) {
      if (nVal < 0) {
        this.show = this.cycle? this.imgs.length - 1: oVal
      } else if (nVal > this.imgs.length - 1) {
        this.show = this.cycle? 0: oVal
      } else {
        if (oVal < 0) this.transitionName = 'left-in'
        else if (oVal > this.imgs.length - 1) this.transitionName = 'right-in'
        else this.transitionName = nVal > oVal ? 'right-in' : 'left-in'
      }
    }
  },
}
</script>

<style lang="postcss" scoped>
    .right-in-enter{
        @apply left-full;
    }
    .right-in-enter-active, .right-in-leave-active{
        transition: left 0.5s;
    }
    .right-in-enter-to, .right-in-leave{
        @apply left-0;
    }
    .right-in-leave-to{
        @apply -left-full;
    }
    .left-in-enter{
        @apply -left-full;
    }
    .left-in-enter-active, .left-in-leave-active{
        transition: left 0.5s;
    }
    .left-in-enter-to, .left-in-leave{
        left:0%
    }
    .left-in-leave-to{
        left:100%
    }
    .wrap {
      @apply relative flex justify-center w-full h-full;
    }
    .container{
        @apply relative overflow-hidden w-full;
        margin: 0 auto;
    }
    .page{
        @apply absolute w-full h-full flex justify-center;
    }
    .navigate-container {
      @apply absolute bottom-[5%] flex justify-center gap-2 lg:gap-5 select-none;
    }
    .navigate-dot {
      @apply rounded-full w-4 h-4 bg-gray-500;
    }
    .navigate-dot-active {
      @apply animate-pulse;
      box-shadow: 0 0 2px 5px #d1d5db;
    }
    .navigate-arrow {
      @apply absolute top-[50%] w-5 h-5  select-none flex justify-center items-center bg-gray-400 text-xl rounded-full text-white;
    }
</style>

但是过渡组似乎在以下代码中不起作用

index.vue

<Carousel>
   <img :src="img.src" v-for="img in imgs" :key="img.src" class="object-cover">
</Carousel>

Carousel.vue (破解版)

<script>
import IconFontVue from '../IconFont.vue'
import NavigationGroupVue from './NavigationGroup.vue'
import SlideVue from './Slide.vue'

export default {
     props :{
        interval: {type: Number, default: 5000},
        autoplay: {type: Boolean, default: false},
        cycle: {type: Boolean, default: true},
        showArrows: {type: Boolean ,default: true},
        showNavigators: {type: Boolean, default: true},
    },
    data(){
        return {
            timer: undefined,
            transitionDirection: 'left-in',
            show: 0,
            children: Array(0),
        }
    },
    mounted () {
        this.children = this.$slots.default
        if(this.autoplay && this.cycle){
            this.timer = setInterval(this.nextShow, this.interval)
        }
    },
    methods: {
        setShow(index){
            this.show = index
        },
        nextShow () {
            this.show++
        }
    },
    watch: {
        show (nVal, oVal) {
        if (nVal < 0) {
            this.show = this.cycle? this.children.length - 1: oVal
        } else if (nVal > this.children.length - 1) {
            this.show = this.cycle? 0: oVal
        } else {
            if (oVal < 0) this.transitionDirection = 'left-in'
            else if (oVal > this.children.length - 1) this.transitionDirection = 'right-in'
            else this.transitionDirection = nVal > oVal ? 'right-in' : 'left-in'
        }
        }
    },
    render(h){
      
      return h('div',{
        class:{
          'wrap':true,
        }
      },[
        // transition group slides
        h('transition-group',{
          props:{
            name: this.transitionDirection,
          },
          class:{
            'containing': true,
          }
        },this.children.map((elem,index)=>{
          return h(SlideVue,{
            key: `slide-${index}`,
            style:{
              'display': this.show===index?'':'none'
            }
          },[elem])
        })),
        // navigation button
        h(NavigationGroupVue,{
          class:{
            'hidden':!this.showNavigators,
          },
          props:{
            show: this.show,
            length: this.children.length,
            setShow: this.setShow,
          },
        }),
        // left button
        h('button',{
          class:{
            'navigate-arrow left-2':true,
            'hidden': !(this.showArrows && (this.cycle || this.show!==0))
          },
          on:{
            click:()=>{
              this.setShow(this.show-1)
            }
          }
        },[
          h(IconFontVue,{
            props:{
              name:"angle-left",
              type:"fas",
            }
          })
        ]),
        //right button
        h('button',{
          class:{
            'navigate-arrow right-2':true,
            'hidden': !(this.showArrows && (this.cycle || this.show!==this.children.length-1))
          },
          on:{
            click:()=>{
              this.setShow(this.show+1)
            }
          }
        },[
          h(IconFontVue,{
            props:{
              name:"angle-right",
              type:"fas",
            }
          })
        ]),

      ])
    }
}
</script>

<style lang="postcss">
   .right-in-enter{
        @apply left-full;
    }
    .right-in-enter-to, .right-in-leave{
        @apply left-0;
    }
    .right-in-leave-to{
        @apply -left-full;
    }
    .left-in-enter{
        @apply -left-full;
    }
    .left-in-enter-active,
    .left-in-leave-active,
    .right-in-enter-active,
    .right-in-leave-active{
        transition: left 0.5s;
    }
    .left-in-enter-to, .left-in-leave{
        @apply left-0;
    }
    .left-in-leave-to{
        @apply left-full;
    }
    .wrap {
      @apply relative flex justify-center w-full h-full;
    }
    .containing{
        @apply relative overflow-hidden w-full;
        margin: 0 auto;
    }
    .page{
        @apply absolute w-full h-full flex justify-center;
    }
    .navigate-container {
      @apply absolute bottom-[5%] flex justify-center gap-2 lg:gap-5 select-none;
    }
    .navigate-dot {
      @apply rounded-full w-4 h-4 bg-gray-500;
    }
    .navigate-dot-active {
      @apply animate-pulse;
      box-shadow: 0 0 2px 5px #d1d5db;
    }
    .navigate-arrow {
      @apply absolute top-[50%] w-5 h-5  select-none flex justify-center items-center bg-gray-400 text-xl rounded-full text-white;
    }
</style>

两段代码编译后类似。但是当我单击箭头或导航按钮时,转换组的类不起作用。有人可以帮我吗?

I'm trying to use Nuxt.js with nuxt/tailwind to build my own Carousel.
I have done a working example, and rewrite the template part by render function.
But I found that the transition-group is not working as previous one.

Here is a simple version, the transition-group tag is not working in render function:

components/Faded.vue

<script>
export default {
  render(h) {
    return h(
      "transition-group",
      {
        props: {
          name: "faded",
        },
      },
      this.$slots.default
    );
  },
};
</script>

<style lang="postcss" scoped>
.faded-leave {
  opacity: 1;
}
.faded-leave-active {
  transition: opacity 1s;
}
.faded-leave-to {
  opacity: 0;
}
.faded-enter {
  opacity: 0;
}
.faded-enter-active {
  transition: opacity 1s;
}
.faded-enter-to {
  opacity: 1;
}
</style>

index.vue

<template>
  <div>
    <button class="bg-green-100" @click="AddIndex">Button</button>
    <Faded>
      <div key="1" v-show="this.showIndex === 0" class="bg-red-100">0</div>
      <div key="2" v-show="this.showIndex === 1" class="bg-yellow-100">1</div>
      <div key="3" v-show="this.showIndex === 2" class="bg-blue-100">2</div>
    </Faded>
  </div>
</template>

<script>
export default {
  data() {
    return {
      showIndex: 0,
    };
  },
  methods: {
    AddIndex() {
      this.showIndex = (this.showIndex + 1) % 3;
    },
  },
};
</script>

original question

This code is working as my expect:

index.vue

<div class="h-screen flex justify-center">
    <div class="h-[60%] lg:w-[50%] w-full bg-black">
      <Carousel />
    </div>
</div>

Carousel.vue(correct version)

<template>
    <div class="wrap">
        <transition-group tag="div" class="container" :name="transitionName">
            <Slide v-for="(img,index) of imgs" :key="img.src" v-show="index===show">
              <img :src="img.src" class="object-cover select-none"/>
            </Slide>
        </transition-group>

        <NavigationGroup v-show="showNavigators" :show="show" :length="imgs.length" :setShow="setShow"/>
        
        <button @click="setShow(show-1)" v-show="showArrows && (cycle || show!==0)"  class="navigate-arrow left-2">
          <IconFont type="fas" name="angle-left"/>
        </button>

        <button @click="setShow(show+1)" v-show="showArrows && (cycle || show!==imgs.length-1)"  class="navigate-arrow right-2">
          <IconFont type="fas" name="angle-right"/>
        </button>
    </div>
</template>

<script>
export default {
  props :{
    interval: {type: Number, default: 5000},
    autoplay: {type: Boolean, default: false},
    cycle: {type: Boolean, default: true},
    showArrows: {type: Boolean ,default: true},
    showNavigators: {type: Boolean, default: true},
  },
  data () {
    return {
      timer: undefined,
      transitionName: 'left-in',
      show: 0,
      imgs: [
        { src: require('~/assets/images/bear.jpg') },
        { src: require('~/assets/images/all-namin.png') },
        { src: require('~/assets/images/eat.png') },
        { src: require('~/assets/images/teemo.jpg') },
        { src: require('~/assets/images/gun.png') },
      ]
    }
  },
  mounted () {
    if(this.autoplay && this.cycle){
      this.timer = setInterval(this.nextShow, this.interval)
    }
  },
  methods: {
    setShow (index) {
      this.show = index
    },
    nextShow () {
      this.show++
    }
  },
  watch: {
    show (nVal, oVal) {
      if (nVal < 0) {
        this.show = this.cycle? this.imgs.length - 1: oVal
      } else if (nVal > this.imgs.length - 1) {
        this.show = this.cycle? 0: oVal
      } else {
        if (oVal < 0) this.transitionName = 'left-in'
        else if (oVal > this.imgs.length - 1) this.transitionName = 'right-in'
        else this.transitionName = nVal > oVal ? 'right-in' : 'left-in'
      }
    }
  },
}
</script>

<style lang="postcss" scoped>
    .right-in-enter{
        @apply left-full;
    }
    .right-in-enter-active, .right-in-leave-active{
        transition: left 0.5s;
    }
    .right-in-enter-to, .right-in-leave{
        @apply left-0;
    }
    .right-in-leave-to{
        @apply -left-full;
    }
    .left-in-enter{
        @apply -left-full;
    }
    .left-in-enter-active, .left-in-leave-active{
        transition: left 0.5s;
    }
    .left-in-enter-to, .left-in-leave{
        left:0%
    }
    .left-in-leave-to{
        left:100%
    }
    .wrap {
      @apply relative flex justify-center w-full h-full;
    }
    .container{
        @apply relative overflow-hidden w-full;
        margin: 0 auto;
    }
    .page{
        @apply absolute w-full h-full flex justify-center;
    }
    .navigate-container {
      @apply absolute bottom-[5%] flex justify-center gap-2 lg:gap-5 select-none;
    }
    .navigate-dot {
      @apply rounded-full w-4 h-4 bg-gray-500;
    }
    .navigate-dot-active {
      @apply animate-pulse;
      box-shadow: 0 0 2px 5px #d1d5db;
    }
    .navigate-arrow {
      @apply absolute top-[50%] w-5 h-5  select-none flex justify-center items-center bg-gray-400 text-xl rounded-full text-white;
    }
</style>

But the transition-group seems not working in the following code

index.vue

<Carousel>
   <img :src="img.src" v-for="img in imgs" :key="img.src" class="object-cover">
</Carousel>

Carousel.vue (broken version)

<script>
import IconFontVue from '../IconFont.vue'
import NavigationGroupVue from './NavigationGroup.vue'
import SlideVue from './Slide.vue'

export default {
     props :{
        interval: {type: Number, default: 5000},
        autoplay: {type: Boolean, default: false},
        cycle: {type: Boolean, default: true},
        showArrows: {type: Boolean ,default: true},
        showNavigators: {type: Boolean, default: true},
    },
    data(){
        return {
            timer: undefined,
            transitionDirection: 'left-in',
            show: 0,
            children: Array(0),
        }
    },
    mounted () {
        this.children = this.$slots.default
        if(this.autoplay && this.cycle){
            this.timer = setInterval(this.nextShow, this.interval)
        }
    },
    methods: {
        setShow(index){
            this.show = index
        },
        nextShow () {
            this.show++
        }
    },
    watch: {
        show (nVal, oVal) {
        if (nVal < 0) {
            this.show = this.cycle? this.children.length - 1: oVal
        } else if (nVal > this.children.length - 1) {
            this.show = this.cycle? 0: oVal
        } else {
            if (oVal < 0) this.transitionDirection = 'left-in'
            else if (oVal > this.children.length - 1) this.transitionDirection = 'right-in'
            else this.transitionDirection = nVal > oVal ? 'right-in' : 'left-in'
        }
        }
    },
    render(h){
      
      return h('div',{
        class:{
          'wrap':true,
        }
      },[
        // transition group slides
        h('transition-group',{
          props:{
            name: this.transitionDirection,
          },
          class:{
            'containing': true,
          }
        },this.children.map((elem,index)=>{
          return h(SlideVue,{
            key: `slide-${index}`,
            style:{
              'display': this.show===index?'':'none'
            }
          },[elem])
        })),
        // navigation button
        h(NavigationGroupVue,{
          class:{
            'hidden':!this.showNavigators,
          },
          props:{
            show: this.show,
            length: this.children.length,
            setShow: this.setShow,
          },
        }),
        // left button
        h('button',{
          class:{
            'navigate-arrow left-2':true,
            'hidden': !(this.showArrows && (this.cycle || this.show!==0))
          },
          on:{
            click:()=>{
              this.setShow(this.show-1)
            }
          }
        },[
          h(IconFontVue,{
            props:{
              name:"angle-left",
              type:"fas",
            }
          })
        ]),
        //right button
        h('button',{
          class:{
            'navigate-arrow right-2':true,
            'hidden': !(this.showArrows && (this.cycle || this.show!==this.children.length-1))
          },
          on:{
            click:()=>{
              this.setShow(this.show+1)
            }
          }
        },[
          h(IconFontVue,{
            props:{
              name:"angle-right",
              type:"fas",
            }
          })
        ]),

      ])
    }
}
</script>

<style lang="postcss">
   .right-in-enter{
        @apply left-full;
    }
    .right-in-enter-to, .right-in-leave{
        @apply left-0;
    }
    .right-in-leave-to{
        @apply -left-full;
    }
    .left-in-enter{
        @apply -left-full;
    }
    .left-in-enter-active,
    .left-in-leave-active,
    .right-in-enter-active,
    .right-in-leave-active{
        transition: left 0.5s;
    }
    .left-in-enter-to, .left-in-leave{
        @apply left-0;
    }
    .left-in-leave-to{
        @apply left-full;
    }
    .wrap {
      @apply relative flex justify-center w-full h-full;
    }
    .containing{
        @apply relative overflow-hidden w-full;
        margin: 0 auto;
    }
    .page{
        @apply absolute w-full h-full flex justify-center;
    }
    .navigate-container {
      @apply absolute bottom-[5%] flex justify-center gap-2 lg:gap-5 select-none;
    }
    .navigate-dot {
      @apply rounded-full w-4 h-4 bg-gray-500;
    }
    .navigate-dot-active {
      @apply animate-pulse;
      box-shadow: 0 0 2px 5px #d1d5db;
    }
    .navigate-arrow {
      @apply absolute top-[50%] w-5 h-5  select-none flex justify-center items-center bg-gray-400 text-xl rounded-full text-white;
    }
</style>

The two codes are similar after compiling. But the classes of transition-group are not working when I click the arrow or navigating button. Can someone please help me ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文