Skip to content

Commit 14723a5

Browse files
committed
feat(quiz): add directional question view transitions
1 parent 5336f4a commit 14723a5

3 files changed

Lines changed: 99 additions & 8 deletions

File tree

assets/src/js/frontend/learning-area/quiz/layout.ts

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export interface QuizLayoutConfig {
1414

1515
const quizLayout = (config: QuizLayoutConfig) => {
1616
const form = window.TutorCore?.form;
17+
const docWithViewTransition = document as Document & {
18+
startViewTransition?: (callback: () => void) => { finished: Promise<void> };
19+
};
1720

1821
return {
1922
layout: config.layout ?? QuizLayoutType.QUESTION_BELOW_EACH_OTHER,
@@ -139,8 +142,10 @@ const quizLayout = (config: QuizLayoutConfig) => {
139142
return;
140143
}
141144
if (this.currentIndex > 1) {
142-
this.currentIndex -= 1;
143-
this.revealFooterState = '';
145+
this.runWithViewTransition(() => {
146+
this.currentIndex -= 1;
147+
this.revealFooterState = '';
148+
}, 'back');
144149
this.scrollToQuestion();
145150
}
146151
},
@@ -174,18 +179,22 @@ const quizLayout = (config: QuizLayoutConfig) => {
174179
const wait = this.getRevealWaitTime();
175180
window.setTimeout(() => {
176181
this.isRevealing = false;
177-
this.revealFooterState = '';
178182
if (this.currentIndex < this.totalQuestions) {
179-
this.currentIndex += 1;
183+
this.runWithViewTransition(() => {
184+
this.currentIndex += 1;
185+
this.revealFooterState = '';
186+
});
180187
this.scrollToQuestion();
181188
}
182189
}, wait);
183190
return;
184191
}
185192

186193
if (this.currentIndex < this.totalQuestions) {
187-
this.currentIndex += 1;
188-
this.revealFooterState = '';
194+
this.runWithViewTransition(() => {
195+
this.currentIndex += 1;
196+
this.revealFooterState = '';
197+
});
189198
this.scrollToQuestion();
190199
}
191200
},
@@ -197,11 +206,35 @@ const quizLayout = (config: QuizLayoutConfig) => {
197206
if (!index || index < 1 || index > this.totalQuestions) {
198207
return;
199208
}
200-
this.currentIndex = index;
201-
this.revealFooterState = '';
209+
this.runWithViewTransition(
210+
() => {
211+
this.currentIndex = index;
212+
this.revealFooterState = '';
213+
},
214+
index < this.currentIndex ? 'back' : 'forward',
215+
);
202216
this.scrollToQuestion();
203217
},
204218

219+
runWithViewTransition(update: () => void, direction: 'forward' | 'back' = 'forward') {
220+
const transitionRoot = document.documentElement;
221+
transitionRoot.style.setProperty('--tutor-quiz-vt-dir', direction === 'back' ? '-1' : '1');
222+
223+
if (!docWithViewTransition.startViewTransition) {
224+
update();
225+
transitionRoot.style.removeProperty('--tutor-quiz-vt-dir');
226+
return;
227+
}
228+
229+
const transition = docWithViewTransition.startViewTransition.call(document, () => {
230+
update();
231+
});
232+
233+
transition.finished.finally(() => {
234+
transitionRoot.style.removeProperty('--tutor-quiz-vt-dir');
235+
});
236+
},
237+
205238
getRevealWaitTime(): number {
206239
const feedbackWaitMs = Number(this.revealWaitMs ?? '');
207240
if (!Number.isNaN(feedbackWaitMs) && feedbackWaitMs > 0) {

assets/src/scss/frontend/learning-area/components/quiz/_quiz.scss

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,59 @@ $tutor-quiz-header-offset-admin: calc(44px + (#{$tutor-spacing-8} * 2) + 46px);
66
$tutor-quiz-header-offset-admin-sm: calc(44px + (#{$tutor-spacing-8} * 2) + 32px);
77
$tutor-quiz-footer-offset: 96px;
88

9+
::view-transition-group(tutor-quiz-question) {
10+
animation-duration: 280ms;
11+
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
12+
}
13+
14+
::view-transition-old(tutor-quiz-question),
15+
::view-transition-new(tutor-quiz-question) {
16+
animation-duration: 280ms;
17+
animation-timing-function: cubic-bezier(0.22, 1, 0.36, 1);
18+
}
19+
20+
::view-transition-old(tutor-quiz-question) {
21+
animation-name: tutor-quiz-question-exit;
22+
}
23+
24+
::view-transition-new(tutor-quiz-question) {
25+
animation-name: tutor-quiz-question-enter;
26+
}
27+
28+
@keyframes tutor-quiz-question-enter {
29+
from {
30+
opacity: 0;
31+
transform: translateX(calc(22px * var(--tutor-quiz-vt-dir, 1))) scale(0.985);
32+
filter: blur(1px);
33+
}
34+
to {
35+
opacity: 1;
36+
transform: translateX(0);
37+
filter: blur(0);
38+
}
39+
}
40+
41+
@keyframes tutor-quiz-question-exit {
42+
from {
43+
opacity: 1;
44+
transform: translateX(0);
45+
filter: blur(0);
46+
}
47+
to {
48+
opacity: 0;
49+
transform: translateX(calc(-22px * var(--tutor-quiz-vt-dir, 1))) scale(0.985);
50+
filter: blur(1px);
51+
}
52+
}
53+
54+
@media (prefers-reduced-motion: reduce) {
55+
::view-transition-group(tutor-quiz-question),
56+
::view-transition-old(tutor-quiz-question),
57+
::view-transition-new(tutor-quiz-question) {
58+
animation-duration: 1ms;
59+
}
60+
}
61+
962
.tutor-quiz {
1063
&-submission {
1164
@include tutor-flex(column);
@@ -346,6 +399,10 @@ $tutor-quiz-footer-offset: 96px;
346399

347400
&-question-wrapper {
348401
width: 100%;
402+
403+
&-active {
404+
view-transition-name: tutor-quiz-question;
405+
}
349406
}
350407

351408
&-skip-btn {

templates/learning-area/quiz/attempt.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class="tutor-quiz-question-wrapper"
201201
data-quiz-question-index="<?php echo esc_attr( $question_index ); ?>"
202202
data-answer-required="<?php echo esc_attr( $answer_required ? '1' : '0' ); ?>"
203203
x-show="isQuestionActive(<?php echo esc_attr( $question_index ); ?>)"
204+
:class="{ 'tutor-quiz-question-wrapper-active': isQuestionActive(<?php echo esc_attr( $question_index ); ?>) }"
204205
x-cloak
205206
>
206207
<?php Quiz::render_question( $question, $question_index ); ?>

0 commit comments

Comments
 (0)