이번에는 Vue가 내부 컴포넌트 캐러셀을 전환하는 단계에 대해 자세히 설명하겠습니다. Vue가 내부 컴포넌트 캐러셀을 전환하는 주의사항은 무엇인가요?
라우팅이 필요하지 않은 내부 컴포넌트의 경우 전환 시 캐러셀 전환 효과를 추가하려고 합니다.
캐러셀 컴포넌트를 도입할 수 있지만 일반적으로 캐러셀에 문제가 있습니다. 모든 슬라이드를 렌더링한 다음 전환하면 모든 리소스가 로드됩니다. 결국 슬라이드가 많으면 로드해야 할 그림과 기타 리소스가 너무 많아질 수 있습니다. 한 번. 따라서 요구 사항을 충족하기 위해 수동으로 간단히 작성할 수 있습니다.
이제 이 기능을 단계별로 구현해 보겠습니다. 먼저 기본 전환을 구현하는 데모를 작성해 보겠습니다.
1. 전환을 구현하려면
먼저 vue-cli를 사용하여 엔지니어링 스캐폴딩을 구축하고 다음 명령을 사용하세요.
npm install -g vue-cli vue init webpack slide-demo # 运行后router等都选择no
그게 다입니다. webpack + vue 프로젝트의 경우, Slide-demo 디렉토리에 들어가서 src/App.vue를 봅니다. 이 파일은 초기화 도구에서 제공되며 전체 페이지의 구성 요소입니다. 하위 구성 요소가 배치되는 디렉터리인 src/comComponents 디렉터리도 있습니다.
이 디렉터리에 task-1.vue, task-2.vue, task-3.vue라는 3개의 새 구성 요소를 만든 다음 아래 App.vue와 같이 App.vue로 가져옵니다.
<script> // import HelloWorld from './components/HelloWorld' import Task1 from "./components/task-1"; import Task2 from "./components/task-2"; import Task3 from "./components/task-3"; export default { name: 'App', components: { Task1, Task2, Task3 } } </script>
Ours 데이터 형식 관련 질문은 다음과 같습니다:
[{index: 1, type: 1, content: ''}, {index: 2, type: 1, content: ''},
{index: 3, type: 2 , content: ''}, {index: 4, type: 3, content: ''}]
배열의 각 요소는 각 질문을 나타내며 각 질문에는 Multiple- 선택 질문, 빈칸 채우기 질문, 판단 질문 등은 각각 위의 task-1, task-2, task-3에 해당하며 현재 어떤 질문인지 표시하고 초기화합니다. 다음 코드(App.vue에 추가됨)에 표시된 대로 이를 0으로 설정합니다.
data() { return { currentIndex: 0 }; }, created() { // 请求question数据 this.questions = [ {index: 1, type: 1, question: ''}, /*...*/]; },
currentIndex 값을 변경하면 다음 구성 요소, 즉 다음 구성 요소로 전환할 수 있습니다. 이 전환 효과를 얻는 방법은 무엇입니까?
Vue에서 사용자 정의한 전역 구성 요소 구성 요소를 is 속성과 결합하여 다음 코드와 같이 구성 요소를 동적으로 변경하는 목적을 달성할 수 있습니다.
<template> <p id="app"> <p class="task-container"> <component :is="'task-' + questions[currentIndex].type" </component> </p> </p> </template>
currentIndex가 증가하면 in:is의 값은 다음과 같습니다. 변경, 순차적으로 작업-1에서 작업-2, 작업-3 등으로 변경하여 구성 요소가 해당 작업 구성 요소로 대체됩니다.
그런 다음 다음 질문으로 전환하는 버튼을 추가하고 이 버튼의 응답 함수에서 currentIndex 값을 변경하세요. 동시에 질문 데이터를 구성 요소에 전달합니다.
<template> <p id="app"> <p class="task-container"> <component :is="'task-' + questions[currentIndex].type" :question="questions[currentIndex]"></component> <button class="next-question" @click="nextQuestion">下一题</button> </p> </p> </template>
응답 함수 nextQuestion은 다음과 같이 구현됩니다.
methods: { nextQuestion() { this.currentIndex = (this.currentIndex + 1) % this.questions.length; } },
각 작업의 구체적인 구현 참조는 task-1.vue 예와 같습니다.
<template> <section> <h2>{{question.index}}. 选择题</h2> <p>{{content}}</p> </section> </template> <script> export default { props: ["question"] } </script>
The 최종 효과는 다음과 같습니다(질문 내용 포함):
2. 캐러셀 전환 효과 추가
캐러셀 전환은 일반적으로 모든 슬라이드를 결합하여 긴 가로 이미지를 형성한 다음 이 가로 이미지의 위치를 변경합니다. 이전 스타일 jQuery 플러그인 Flipsnap.js와 같은 디스플레이 컨테이너에서는 모든 슬라이드를 부동으로 설정합니다. 왼쪽으로 긴 그림을 형성한 다음 이 긴 그림의 번역 값을 변경하여 전환 목적을 달성합니다. . 이 플러그인의 단점은 마지막 그림에서 첫 번째 그림으로 다시 전환할 수 있는 방법이 없다는 것입니다. 이 문제를 해결하는 한 가지 방법은 DOM을 지속적으로 이동하는 것입니다. 전환할 때마다 첫 번째 그림을 마지막 그림 뒤로 이동합니다. picture, 그래서 마지막 사진을 클릭하면 첫 번째 사진으로 돌아가는 목적은 달성하지만 이렇게 이리저리 옮기는 것은 성능을 많이 소모하고 별로 우아하지 않습니다. 또 다른 캐러셀 플러그인인 jssor 슬라이더도 모든 슬라이드를 렌더링한 다음 전체 긴 이미지의 위치 대신 전환될 때마다 각 슬라이드의 번역 값을 동적으로 계산하므로 이동할 필요가 없습니다. DOM 노드는 비교적 우아합니다. 위에서 언급한 것과 비슷한 방식으로 구현되는 Vue 캐러셀 플러그인도 많이 있습니다. 不管怎么样,上面的轮播模式都不太适用于我们的场景,其中一个是这种答题的场景不需要切回上一题,每道题做完就不能回去了,更重要的一个是我们 不希望一次性把所有的slide都渲染出来 ,这样会导致每张幻灯片里的资源都触发加载,就比如img标签虽然你把它display: none了,但是只要它的src是一个正常的url,它就会请求加载。 由于slide往往会比较多,就不使用这种轮播插件了。 还可以使用Vue自带的transition,但是transition的问题是,切下一个的时候,上一个不见了,因为被销毁了,只有下一个的动画,并且不能预加载下一个slide的资源。 所以我们手动实现一个。 我的想法是每次都准备两个slide,第1个slide是当前展示用的,第2个slide拼在它的后面,准备切过来,当第2个slide切过来之后,删掉第1个slide,然后在第2个的后面再接第3个slide,不断地重复这个过程。如果我们没有使用Vue,而是自己增删DOM,那么没什么问题,可以很任性地自己发挥。 使用Vue可以怎么优雅地实现这个功能呢 ? 在上面一个component的基础上,再添加一个component,刚开始第1个component是当前展示的,而第2个component是拼在它右边的,当第2个切过去之后,就把第1个移到第2的后面,同时把内容改成第3个slide的内容,依此类推。使用Vue不太好动态地改DOM,但是可以 借助jssor slider的思想 ,不移动DOM,只是改变component的translate的值。 给其中一个component套一个next-task的类,具有这个类的组件就表示它是下一张要出现的,它需要translateX(100%),如下代码所示: 上面代码把具有.next-task类的component隐藏了,这样是做个优化,因为display: none的元素只会构建DOM,不会进行layout和render渲染。 所以就把问题转换成怎么在这两个component之间,切换next-task的类。一开始next-task是在第2个,当第2个切过来之后,next-task变成加在第1个上面,这样轮流交替。 进而,发现一个规律,如果currentIndex是偶数话,如o、2、4…,那么next-task是加在第2个component的,而如果currentIndex是奇数,则next-task是加在第1个component的。所以可以根据currentIndex的奇偶性切换。 如下代码所示: 第1个component用来显示偶数的slide,第2个是用来显示奇数的slide(分别用一个evenIndex和oddIndex代表),如果nextIndex是偶数的,那么偶数的component就会有一个next-task的类,反之则亦然。然后在下一题按钮的响应函数里面改变这几个index的值: nextQuestion函数可能还有其它一些处理,在它里面调一下_slideToNext函数,这个函数的实现如下: 代码把下一个slide的display改成block,并把它的translateX的值置为0,这个时候不能马上更新数据触发DOM更新,要等到下一个slide移过去的动画结束之后再开始操作,所以加了一个setTimeout,在回调里面调换nextTask的类,加到原本的current slide,并把它的内容置成下下张的内容。这些都是通过改变相应的index完成的。 这样基本上就完成了,但是我们发现一个问题,切是切过去了,就是没有动画效果。这个是因为从display: none变到display: block是没有动画的,要么改成visibility: hidden到visible,要么触发动画的操作加到$nextTick或者setTimeout 0里面,考虑到性能问题,这里使用第二种方案: 经过这样的处理之后,点下一题就有动画了,但是又发现一个问题,就是偶数的next-task会被盖住,因为偶数的是使用第一个component,它是会被第二个compoent盖住的,所以需要给它加一个z-index: 这个问题还比较好处理,另外一个不太好处理的问题是:动画的时间是0.5s,如果用户点下一题的速度很快在0.5s之内,上面的代码执行就会有问题,会导致数据错乱。如果每次切到下一题之后按钮初始化都是disabled,因为当前题还没答,只有答了才能变成可点状态,可以保证0.5s的时间是够的,那么就可以不用考虑这种情况。但是如果需要处理这种情况呢? 3. 解决点击过快的问题 我想到两个方法,第一个方法是用一个sliding的变量标志当前是否是在进行切换的动画,如果是的话,点击按钮的时候就直接更新数据,同时把setTimeout 0.5s的计时器清掉。这个方法可以解决数据错乱的问题,但是切换的效果没有了,或者是切换到一半的时候突然就没了,这样体验不是很好。 第二个方法是延后切换,即如果用户点击过快的时候,把这些操作排队,等着一个个做切换的动画。 我们用一个数组表示队列,如果当前已经在做滑动的动画,则入队暂不执行动画,如下代码所示: 每次点击按钮都把待处理的currentIndex插到队列里面,如果当前已经在滑动了,则不立刻执行,否则执行滑动_slideToNext函数: 这个函数每次都先取出当前要处理的currentIndex,然后接下来的操作和第2点提到的一样,只是在0.5s动画结束后的异步回调里面需要判断一下,当前队列是否还有未处理的元素,如果有的话,需要继续执行_slideToNext,直到队列空了。这个执行需要挂在nextTick里面,因为需要等到两个component的DOM更新了才能操作。 这样理论上就没问题了,但实际上还是有问题,感受如下: 我们发现有些slide没有过渡效果,而且不是非必现的,没有规律。经过一番排查,发现如果把上面的nextTick改成setTimeout情况就会好一些,并且setTimeout的时间越长,就越不会出现失去过渡效果的情况。但是这个不能从根本上解决问题,这里的原因应该是Vue的自动更新DOM和transition动画不是很兼容,有可能是Vue的异步机制问题,也有可能是JS结合transition本身就有问题,但以前没有遇到过,具体没有深入排查。不管怎么样,只能放弃使用CSS的transition做动画。 如果有使用jQuery的话,可以使用jQuery的animation,如果没有的话,那么可以使用原生dom的animate函数,如下代码所示: 使用animate函数达到了和transition同样的效果,并且还有一个onfinish的动画结束回调函数。上面代码还做了一个优化,如果用户点得很快的时候,缩短过渡动画的时间,让它切得更快一点,这样看起来更自然一点。使用这样的方式,就不会出现transition的问题了。最后的效果如下: 这个体验感觉已经比较流畅了。 原生animate不兼容IE/Edge/Safari,可以装一个polyfill的库,如这个 web-animation ,或者使用其它一些第三方的动画库,或自己用setInterval写一个。 如果你要加上一题的按钮,支持返回上一题,那么可能需要准备3个component,中间那个用于显示,左右两边各跟着一个,准备随时切过来。具体读者可以自行尝试。 这种模式除了答题的场景,还有多封邮件预览、PPT展示等都可以用到,它除了有一个过渡的效果外,还能提前预加载下一个slide需要的图片、音频、视频等资源,并且不会像传统的轮播插件那样一下子把所有的slide都渲染了。适用于slide比较多的情况,不需要太复杂的切换动画。 相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章! 推荐阅读:<template>
<p id="app">
<p class="task-container">
<component :is="'task-' + questions[currentIndex].type"
></component>
<component :is="'task-' + questions[currentIndex + 1].type"
class="next-task"></component>
</p>
</p>
</template>
<style>
.next-task {
display: none;
transform: translateX(100%);
/* 添加一个动画,当改变transform的值时就会触发这个动画 */
transition: transform 0.5s ease;
}
</style>
<template>
<p id="app">
<p class="task-container">
<component :is="'task-' + questions[evenIndex].type"
:class="{'next-task': nextIndex === evenIndex}"
ref="evenTask"></component>
<component :is="'task-' + questions[oddIndex].type"
:class="{'next-task': nextIndex === oddIndex}
ref="oddTask"></component>
</p>
</p>
</template>
<script>
export default {
name: 'App',
data() {
return {
currentIndex: 0, // 当前显示的index
nextIndex: 1, // 表示下一张index,值为currentIndex + 1
evenIndex: 0, // 偶数的index,值为currentIndex或者是currentIndex + 1
oddIndex: 1 // 奇数的index,值同上
};
},
}
methods: {
nextQuestion() {
this.currentIndex = (this.currentIndex + 1)
% this.questions.length;
this._slideToNext();
},
// 切到下一步的动画效果
_slideToNext() {
}
}
_slideToNext() {
// 当前slide的类型(currentIndex已经加1了,这里要反一下)
let currentType = this.currentIndex % 2 ? "even" : "odd",
// 下一个slide的类型
nextType = this.currentIndex % 2 ? "odd": "even";
// 获取下一个slide的dom元素
let $nextSlide = this.$refs[`${nextType}Task`].$el;
$nextSlide.style.display = "block";
// 把下一个slide的translate值置为0,原本是translateX(100%)
$nextSlide.style.transform = "translateX(0)";
// 等动画结束后更新数据
setTimeout(() => {
this.nextIndex = (this.currentIndex + 1)
% this.questions.length;
// 原本的next是当前显示的slide
this[`${nextType}Index`] = this.currentIndex;
// 而原本的current slide要显示下下张的内容了
this[`${currentType}Index`] = this.nextIndex;
}, 500);
}
$nextSlide.style.display = "block";
// 这里使用setimeout,因为$nextTick有时候没有动画,非必现
setTimeout(() => {
$nextSlide.style.transform = "translateX(0)";
// ...
}, 0);
.next-task {
display: none;
transform: translateX(100%);
transition: transform 0.5s ease;
z-index: 2;
}
methods: {
nextQuestion() {
this.currentIndex = (this.currentIndex + 1)
% this.questions.length;
// 把currentIndex插到队首
this.slideQueue.unshift(this.currentIndex);
// 如果当前没有滑动,则执行滑动
!this.sliding && this._slideToNext();
},
}
_slideToNext() {
// 取出下一个要处理的元素
let currentIndex = this.slideQueue.pop();
// 下一个slide的类型
let nextType = currentIndex % 2 ? "odd" : "even";
let $nextSlide = this.$refs[`${nextType}Task`].$el;
$nextSlide.style.display = "block";
setTimeout(() => {
$nextSlide.style.transform = "translateX(0)";
this.sliding = true;
setTimeout(() => {
this._updateData(currentIndex);
// 如果当前还有未处理的元素,
// 则继续处理即继续滑动
if (this.slideQueue.length) {
// 要等到两个component的DOM更新了
this.$nextTick(this._slideToNext);
} else {
this.sliding = false;
}
}, 500);
}, 0);
},
_slideToNext(fast = false) {
let currentIndex = this.slideQueue.pop();
// 下一个slide的类型
let nextType = currentIndex % 2 ? "odd" : "even";
// 获取下一个slide的dom元素
let $nextSlide = this.$refs[`${nextType}Task`].$el;
$nextSlide.style.display = "block";
this.sliding = true;
// 使用原生animate函数
$nextSlide.animate([
// 关键帧
{transform: "translateX(100%)"},
{transform: "translateX(0)"}
], {
duration: fast ? 200 : 500,
iteration: 1,
easing: "ease"
// 返回一个Animate对象,它有一个onfinish的回调
}).onfinish = () => {
// 等动画结束后更新数据
this._updateData(currentIndex);
if (this.slideQueue.length) {
this.$nextTick(() => {
this._slideToNext(true);
});
} else {
this.sliding = false;
}
};
},
위 내용은 Vue에서 내부 구성 요소 캐러셀을 전환하는 단계에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!