Для тех внутренних компонентов, которые не нуждаются в роутинге, нужно добавить эффект карусельного перехода при переключении, эффект следующий:
Мы можем ввести компонент карусели, но есть проблема, обычно компонент карусели будет отображать все слайды, а затем переключаться, что приведет к запуску загрузки всех ресурсов, что может быть не тем, что мы ожидаем, в конце концов, если слайд Во многих случаях , слишком много изображений и других ресурсов, которые необходимо загрузить одновременно. Таким образом, мы можем просто написать его вручную, чтобы удовлетворить потребности.
Теперь, чтобы реализовать эту функцию шаг за шагом, сначала напишите демонстрацию, которая реализует базовое переключение.
1. Реализация переключателя
Сначала используйте vue-cli для создания каркаса проекта, используйте следующие команды:
npm install -g vue-cli
vue init webpack slide-demo # 运行后router等都选择no
Таким образом создается проект webpack + vue, войдите в каталог slide-demo и просмотрите src/App.vue, Этот файл предоставляется инструментом инициализации и является компонентом всей страницы. Существует также каталог src/components, в котором размещаются подкомпоненты.
Создайте в этом каталоге три новых компонента: task-1.vue, task-2.vue, task-3.vue, а затем импортируй их в 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>
Наши вопросы по формату данных выглядят так:
[{index: 1, type: 1, content: ''}, {index: 2, type: 1, content: ''},
{index: 3, type: 2, content: ''}, {index: 4, type: 3, content: ''}]
Это массив, каждый элемент которого представляет каждый вопрос, и каждый вопрос имеет тип, такой как вопросы с несколькими вариантами ответов, вопросы с заполнением пропусков, истинные или ложные вопросы и т. д., соответствующие вышеуказанной задаче. 1, задача-2, задача-3 соответственно, мы используем переменную currentIndex, чтобы указать, какой вопрос находится в данный момент, инициализируя его значением 0, как показано в следующем коде (добавленном в App.vue):
data() {
return {
currentIndex: 0
};
},
created() {
// 请求question数据
this.questions = [
{index: 1, type: 1, question: ''}, /*...*/];
},
Изменив значение currentIndex, мы можем переключиться на следующий компонент, то есть как добиться такого эффекта переключения?
Вы можете использовать настраиваемый глобальный компонент Vue Component, задав его атрибут IS, чтобы достичь цели динамического изменения компонентов, как показано ниже:
<template>
<div id="app">
<div class="task-container">
<component :is="'task-' + questions[currentIndex].type"
</component>
</div>
</div>
</template>
Когда CUTENINGINDEX увеличивается, он изменит значение в: IS, из задачи - 1 для задачи - 2, задача-3 и т. Д., Чтобы компонент был заменен соответствующей компонентом задач.
Затем добавьте кнопку для переключения на следующий вопрос и измените значение currentIndex в функции ответа кнопки. При этом передаем данные вопроса в компонент:
<template>
<div id="app">
<div class="task-container">
<component :is="'task-' + questions[currentIndex].type"
:question="questions[currentIndex]"></component>
<button class="next-question" @click="nextQuestion">下一题</button>
</div>
</div>
</template>
Функция ответа nextQuestion реализована следующим образом:
methods: {
nextQuestion() {
this.currentIndex = (this.currentIndex + 1)
% this.questions.length;
}
},
Для конкретной реализации каждой задачи обратитесь к примеру task-1.vue:
<template>
<section>
<h2>{{question.index}}. 选择题</h2>
<div>{{content}}</div>
</section>
</template>
<script>
export default {
props: ["question"]
}
</script>
Окончательный эффект выглядит следующим образом (плюс содержание заголовка):
2. Добавьте эффект переключения карусели
Переключение карусели обычно заключается в объединении всех слайдов в длинное горизонтальное изображение, а затем изменении положения горизонтального изображения в контейнере отображения, например, в старом jQuery-плагине flipsnap.js, который заставляет все слайды плавать: влево, образуя длинный график, тоИзмените значение перевода этого длинного изображения, для достижения цели переключения. Недостаток этого плагина в том, что нет возможности сократить от последнего к первому, и один из способов решить эту проблему — постоянно перемещать DOM: каждый раз, когда вы режете, перемещайте первый в конец last, так что Цель возврата к первому, когда последний щелкает следующий, достигается, но перемещение его таким образом потребляет много производительности и не очень элегантно. Еще один плагин-карусель jssor слайдер, он тоже рендерит все слайды, а потом каждый раз переключается.Динамически рассчитывать значение перевода каждого слайда, а не положение всего длинного изображения, так что вам не нужно перемещать узел DOM, что относительно элегантно. Также существует множество карусельных плагинов Vue, которые реализованы так же, как упоминалось выше.
В любом случае, приведенный выше режим карусели не очень подходит для нашей сцены.Одним из них является то, что такая сцена ответов не требует переключения обратно на предыдущий вопрос.После выполнения каждого вопроса мы не можем вернуться назад.Что более важно один в том, что мыЯ не хочу отображать все слайды сразу, что приведет к тому, что ресурсы на каждом слайде вызовут загрузку.Например, хотя вы поместите display: none в тег img, если его src является обычным URL-адресом, он будет запрашивать загрузку. Поскольку слайдов часто бывает много, такой плагин карусели не используется.
Вы также можете использовать переход, который идет с Vue, но проблема с переходом в том, что когда следующий вырезается, предыдущий исчезает, потому что он уничтожается, остается только следующая анимация, а ресурсы следующего слайда не может быть предварительно загружен.
Поэтому мы реализуем его вручную.
Моя идея состоит в том, чтобы каждый раз готовить два слайда, первый слайд предназначен для текущего отображения, второй слайд написан за ним по буквам, готовый к вырезанию, когда второй слайд обрезан, удалите первый слайд слайда, затем добавьте третий слайд после второго, и повторяйте процесс снова и снова. Если мы не используем Vue, а сами добавляем и удаляем DOM, то проблем нет, и мы можем играть сами.Как можно элегантно реализовать эту функцию с помощью Vue??
На основе вышеприведенного компонента добавить еще один компонент.В начале в данный момент отображается первый компонент, а справа от него прописан второй компонент.Когда второй компонент будет вырезан, переместите первый.Перейти к заднюю часть второго слайда и измените содержимое на содержимое третьего слайда и т. д. Использование Vue не очень хорошо для динамического изменения DOM, но вы можетеС идеей слайдера jssor, не перемещает DOM, просто изменяет значение перевода компонента.
Установите класс следующей задачи для одного из компонентов. Компонент с этим классом означает, что он появится следующим. Ему требуется translateX (100%), как показано в следующем коде:
<template>
<div id="app">
<div class="task-container">
<component :is="'task-' + questions[currentIndex].type"
></component>
<component :is="'task-' + questions[currentIndex + 1].type"
class="next-task"></component>
</div>
</div>
</template>
<style>
.next-task {
display: none;
transform: translateX(100%);
/* 添加一个动画,当改变transform的值时就会触发这个动画 */
transition: transform 0.5s ease;
}
</style>
Приведенный выше код скрывает компонент с классом .next-task, что является оптимизацией, потому что элемент с display: none будет только строить DOM, а не выполнять компоновку и визуализацию рендеринга.
Поэтому превратите проблему в то, как переключить класс следующей задачи между этими двумя компонентами. В начале следующая-задача находится во второй, при перерезании второй к первой добавляется следующая-задача, которая чередуется.
Кроме того, найдено правило: если currentIndex — четное число, например, o, 2, 4..., то следующая задача добавляется ко второму компоненту, а если currentIndex — нечетное число, следующая- задача добавлена к первому компоненту. Таким образом, его можно переключать в соответствии с четностью currentIndex.
Как показано в следующем коде:
<template>
<div id="app">
<div 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>
</div>
</div>
</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,值同上
};
},
}
Первый компонент используется для отображения четного числа слайдов, а второй — для отображения нечетного СЛАЙДА (представленного EVENIDEX и ODDINDEX).Если NextIndex четный, то четный компонент будет иметь класс NEXT-TASK. Это важно. Затем измените значение этих индексов в функции ответа кнопки следующего вопроса:
methods: {
nextQuestion() {
this.currentIndex = (this.currentIndex + 1)
% this.questions.length;
this._slideToNext();
},
// 切到下一步的动画效果
_slideToNext() {
}
}
Функция nextQuestion и, возможно, какая-то другая обработка, в ней настроена функция _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);
}
Код изменяет отображение следующего слайда на блок и устанавливает значение его translateX в 0. В это время данные не могут быть обновлены немедленно, чтобы вызвать обновление DOM, и операция должна дождаться анимации следующего слайда. закончилась до начала операции, поэтому добавила setTimeout, поменяйте местами класс nextTask в обратном вызове, добавьте его к исходному текущему слайду и установите его содержимое на содержимое следующего листа. Это делается путем изменения соответствующего индекса.
На этом в принципе доделано, но мы обнаружили проблему, разрез обрезается мимо, то есть нет эффекта анимации. Это связано с тем, что при переходе с display: none на display: block анимации нет. Либо измените с visible: hidden на visible, либо добавьте операцию, запускающую анимацию, в $nextTick или setTimeout 0. Учитывая проблемы с производительностью, второй вариант используется здесь.план:
$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;
}
Эта проблема относительно легко решается.Еще одна проблема, с которой нелегко справиться, это: время анимации составляет 0,5 с.Если пользователь быстро нажмет на следующий вопрос в течение 0,5 с, возникнут проблемы с выполнением приведенного выше кода. y привести к повреждению данных. Если инициализация кнопки отключена каждый раз после перехода к следующему вопросу, потому что на текущий вопрос не было ответа, и только после того, как ответ может быть переведен в кликабельное состояние, можно гарантировать, что времени 0,5 с достаточно, то это ситуация может быть проигнорирована. Но что, если эту ситуацию нужно разрешить?
3. Решите проблему слишком быстрого нажатия
Я думаю о двух методах. Первый метод - использовать скользящую переменную, чтобы отметить, переключается ли анимация в данный момент. Если это так, данные будут обновляться непосредственно при нажатии кнопки, а таймер setTimeout 0,5 с будет очищен в то же время. Этот метод может решить проблему путаницы данных, но эффект переключения пропал, или он внезапно исчезает на полпути, поэтому опыт не очень хороший.
Второй способ — отложить переключение, то есть, если пользователь нажимает слишком быстро, поставить эти операции в очередь и дождаться переключения анимаций одну за другой.
Мы используем массив для представления очереди.Если в данный момент выполняется скользящая анимация, анимация временно не будет выполняться при входе в очередь, как показано в следующем коде:
methods: {
nextQuestion() {
this.currentIndex = (this.currentIndex + 1)
% this.questions.length;
// 把currentIndex插到队首
this.slideQueue.unshift(this.currentIndex);
// 如果当前没有滑动,则执行滑动
!this.sliding && this._slideToNext();
},
}
При каждом нажатии на кнопку в очередь вставляется незавершенный currentIndex.Если он в данный момент скользит, то он не будет выполняться сразу, иначе будет выполнена функция slide_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);
},
Эта функция сначала извлекает currentIndex для обработки каждый раз, а затем следующая операция такая же, как указано в пункте 2, за исключением того, что в асинхронном обратном вызове после окончания анимации 0,5 с необходимо оценить, есть ли еще необработанные в текущей очереди. Элементы, если они есть, должны продолжать выполнение _slideToNext до тех пор, пока очередь не станет пустой. Это выполнение должно быть приостановлено в nextTick, потому что для работы ему нужно дождаться обновления DOM двух компонентов.
Теоретически это не проблема, но на практике все же есть проблема, ощущения следующие:
Мы обнаружили, что некоторые слайды не имеют эффектов перехода, и они не являются обязательными и нерегулярными. После некоторого исследования выясняется, что если вышеуказанный nextTick изменить на setTimeout, ситуация будет лучше, и чем больше время setTimeout, тем меньше будет потерян эффект перехода. Но это не может кардинально решить проблему. Причина здесь должна заключаться в том, что автоматическое обновление DOM Vue и анимация перехода не очень совместимы. Это может быть проблема с асинхронным механизмом Vue, или это может быть проблема с JS в сочетании с самим переходом. , но я не сталкивался с этим раньше. Я был там, но не исследовал подробно. В любом случае, вы можете отказаться только от использования CSS-переходов для анимации.
Если вы используете jQuery, вы можете использовать анимацию jQuery, если нет, вы можете использовать встроенную функцию анимации DOM, как показано в следующем коде:
_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;
}
};
},
Использование функции анимации дает тот же эффект, что и переход, а также имеет функцию обратного вызова завершения анимации. Приведенный выше код также подвергся оптимизации: если пользователь быстро нажимает, уменьшите время анимации перехода и сделайте ее более быстрой, чтобы она выглядела более естественно. Таким образом, проблем с переходом не будет. Окончательный эффект выглядит следующим образом:
Опыт кажется более плавным.
Собственная анимация несовместима с IE/Edge/Safari, вы можете установить библиотеку полифилла, например этуweb-animation, или используйте другие сторонние библиотеки анимации, или напишите сами с помощью setInterval.
Если вы хотите добавить кнопку для вопроса, чтобы поддерживать возврат к предыдущему вопросу, вам может потребоваться подготовить 3 компонента: средний используется для отображения, за ним следуют по одному слева и справа, готовые к вырезанию в любое время. Конкретные читатели могут попробовать это сами.
Этот режим можно использовать в дополнение к сценам ответов на вопросы, а также множественным предварительным просмотрам электронной почты, презентациям PPT и т. д. В дополнение к эффекту перехода он также может предварительно загружать изображения, аудио, видео и другие ресурсы, необходимые для следующего. слайд заранее, и он не будет отображать все слайды сразу, как традиционный плагин карусели. Он подходит для ситуаций, когда много слайдов и не требует слишком сложных анимаций переключения.
[Вне номера] «Эффективный внешний интерфейс» был указан, доступен на Jingdong, Amazon, Taobao и т. д.