@gudhub/ssg-web-components-library 1.0.117 → 1.0.118
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/get-in-touch-form/get-in-touch-form.html +0 -3
- package/src/components/get-in-touch-form/get-in-touch-form.js +16 -72
- package/src/components/get-in-touch-form/get-in-touch-form.scss +0 -12
- package/src/components/video-slider/animations/cascade-strips.js +89 -0
- package/src/components/video-slider/animations/fade.js +31 -0
- package/src/components/video-slider/animations/multi-strip.js +66 -0
- package/src/components/video-slider/animations/sectors.js +104 -0
- package/src/components/video-slider/animations/wipe-left.js +43 -0
- package/src/components/video-slider/animations/zoom.js +51 -0
- package/src/components/video-slider/config.js +6 -0
- package/src/components/video-slider/utils/draw-image-cover.js +27 -0
- package/src/components/video-slider/utils/easing.js +5 -0
- package/src/components/video-slider/video-slider.html +5 -0
- package/src/components/video-slider/video-slider.js +177 -0
- package/src/components/video-slider/video-slider.readme.md +218 -0
- package/src/components/video-slider/video-slider.scss +16 -0
- package/src/config.js +1 -2
package/package.json
CHANGED
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
<button type="submit" class="btn">${buttonText ? buttonText : 'Get in touch'}</button>
|
|
11
11
|
<div class="loader"></div>
|
|
12
12
|
</div>
|
|
13
|
-
<div class="recaptcha-notice">
|
|
14
|
-
This site is protected by reCAPTCHA.
|
|
15
|
-
</div>
|
|
16
13
|
</form>
|
|
17
14
|
<div class="overflow success">
|
|
18
15
|
<div class="icon_wrapper">
|
|
@@ -5,7 +5,6 @@ import defaultConfigs from './get-in-touch-form-data.json';
|
|
|
5
5
|
import { checkInputsValidations } from './inputsValidation.js';
|
|
6
6
|
|
|
7
7
|
let recaptchaPromise = null;
|
|
8
|
-
let recaptchaBadgeObserver = null;
|
|
9
8
|
|
|
10
9
|
function loadRecaptcha(siteKey) {
|
|
11
10
|
if (!siteKey) {
|
|
@@ -36,41 +35,16 @@ function loadRecaptcha(siteKey) {
|
|
|
36
35
|
return recaptchaPromise;
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
function hideRecaptchaBadge() {
|
|
40
|
-
const badge = document.querySelector('.grecaptcha-badge');
|
|
41
|
-
|
|
42
|
-
if (!badge) return;
|
|
43
|
-
|
|
44
|
-
badge.style.setProperty('visibility', 'hidden', 'important');
|
|
45
|
-
badge.style.setProperty('opacity', '0', 'important');
|
|
46
|
-
badge.style.setProperty('pointer-events', 'none', 'important');
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function observeRecaptchaBadge() {
|
|
50
|
-
hideRecaptchaBadge();
|
|
51
|
-
|
|
52
|
-
if (recaptchaBadgeObserver) return;
|
|
53
|
-
|
|
54
|
-
recaptchaBadgeObserver = new MutationObserver(() => {
|
|
55
|
-
hideRecaptchaBadge();
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
recaptchaBadgeObserver.observe(document.body, {
|
|
59
|
-
childList: true,
|
|
60
|
-
subtree: true
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
|
|
64
38
|
class GetInTouchForm extends GHComponent {
|
|
65
39
|
constructor() {
|
|
66
40
|
super();
|
|
67
41
|
|
|
68
|
-
this.formId = this.getAttribute(
|
|
42
|
+
this.formId = this.getAttribute("data-form-id");
|
|
69
43
|
this.defaultConfigs = defaultConfigs;
|
|
70
44
|
|
|
71
45
|
this.generateInput = this.generateInput;
|
|
72
46
|
this.isFormSubmitted = false;
|
|
73
|
-
|
|
47
|
+
|
|
74
48
|
this.placement = 'main';
|
|
75
49
|
this.config = window.getConfig()?.componentsConfigs?.formConfig || window.getConfig()?.formConfig;
|
|
76
50
|
|
|
@@ -92,11 +66,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
92
66
|
this.attachEventListeners();
|
|
93
67
|
|
|
94
68
|
if (this.recaptcha_site_key) {
|
|
95
|
-
loadRecaptcha(this.recaptcha_site_key)
|
|
96
|
-
.then(() => {
|
|
97
|
-
observeRecaptchaBadge();
|
|
98
|
-
})
|
|
99
|
-
.catch(console.error);
|
|
69
|
+
loadRecaptcha(this.recaptcha_site_key).catch(console.error);
|
|
100
70
|
}
|
|
101
71
|
}
|
|
102
72
|
}
|
|
@@ -106,26 +76,15 @@ class GetInTouchForm extends GHComponent {
|
|
|
106
76
|
super.render(html);
|
|
107
77
|
this.attachEventListeners();
|
|
108
78
|
|
|
109
|
-
|
|
110
|
-
loadRecaptcha(this.recaptcha_site_key)
|
|
111
|
-
.then(() => {
|
|
112
|
-
observeRecaptchaBadge();
|
|
113
|
-
})
|
|
114
|
-
.catch(console.error);
|
|
115
|
-
}
|
|
79
|
+
loadRecaptcha(this.recaptcha_site_key).catch(console.error);
|
|
116
80
|
}
|
|
117
81
|
|
|
118
82
|
attachEventListeners() {
|
|
119
|
-
|
|
120
|
-
|
|
83
|
+
this.getElementsByTagName('form')[0]
|
|
84
|
+
.addEventListener('submit', (e) => this.handleSubmit(e));
|
|
121
85
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (restartButton) {
|
|
127
|
-
restartButton.addEventListener('click', () => this.hideFail());
|
|
128
|
-
}
|
|
86
|
+
this.getElementsByClassName('restart_button')[0]
|
|
87
|
+
.addEventListener('click', () => this.hideFail());
|
|
129
88
|
}
|
|
130
89
|
|
|
131
90
|
onParentPopupClose() {
|
|
@@ -140,7 +99,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
140
99
|
initConfig(formConfigs) {
|
|
141
100
|
try {
|
|
142
101
|
this.config = formConfigs.find(({ id }) => id === this.formId);
|
|
143
|
-
if (!this.config) throw new Error(
|
|
102
|
+
if (!this.config) throw new Error("Config not found");
|
|
144
103
|
} catch {
|
|
145
104
|
const defaultId = this.isInPopup ? 'default popup' : 'default';
|
|
146
105
|
this.config = defaultConfigs.find(({ id }) => id === defaultId);
|
|
@@ -148,7 +107,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
148
107
|
|
|
149
108
|
this.titleName = this.getAttribute('data-form-title') || this.config.title;
|
|
150
109
|
this.subtitleName = this.getAttribute('data-form-subtitle') || this.config.subtitle;
|
|
151
|
-
this.placement = this.getAttribute('data-form-placement') ||
|
|
110
|
+
this.placement = this.getAttribute('data-form-placement') || "main";
|
|
152
111
|
this.buttonText = this.getAttribute('data-form-button-text') || this.config.button_text;
|
|
153
112
|
}
|
|
154
113
|
|
|
@@ -162,8 +121,6 @@ class GetInTouchForm extends GHComponent {
|
|
|
162
121
|
|
|
163
122
|
return new Promise((resolve, reject) => {
|
|
164
123
|
grecaptcha.ready(() => {
|
|
165
|
-
hideRecaptchaBadge();
|
|
166
|
-
|
|
167
124
|
grecaptcha.execute(this.recaptcha_site_key, { action })
|
|
168
125
|
.then(resolve)
|
|
169
126
|
.catch(reject);
|
|
@@ -189,6 +146,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
189
146
|
}
|
|
190
147
|
|
|
191
148
|
this.handleSuccessFormValidation(form, token);
|
|
149
|
+
|
|
192
150
|
} catch (error) {
|
|
193
151
|
console.error(error);
|
|
194
152
|
this.showFail();
|
|
@@ -221,7 +179,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
221
179
|
inputsValidation = async (form) => {
|
|
222
180
|
const inputs = Array.from(form.querySelectorAll('input'));
|
|
223
181
|
return checkInputsValidations(inputs);
|
|
224
|
-
}
|
|
182
|
+
}
|
|
225
183
|
|
|
226
184
|
verifyRecaptcha = async (recaptchaToken) => {
|
|
227
185
|
const response = await fetch('/api/verify-recaptcha', {
|
|
@@ -242,7 +200,7 @@ class GetInTouchForm extends GHComponent {
|
|
|
242
200
|
}
|
|
243
201
|
|
|
244
202
|
return data;
|
|
245
|
-
}
|
|
203
|
+
}
|
|
246
204
|
|
|
247
205
|
createDataObject(form, placement, recaptchaToken) {
|
|
248
206
|
const inputs = {};
|
|
@@ -272,24 +230,13 @@ class GetInTouchForm extends GHComponent {
|
|
|
272
230
|
|
|
273
231
|
addLoader() {
|
|
274
232
|
this.classList.add('loading');
|
|
275
|
-
|
|
276
|
-
const btn = this.querySelector('button[type="submit"]');
|
|
277
|
-
|
|
278
|
-
if (btn) {
|
|
279
|
-
btn.disabled = true;
|
|
280
|
-
}
|
|
233
|
+
this.querySelector('button[type="submit"]').disabled = true;
|
|
281
234
|
}
|
|
282
235
|
|
|
283
236
|
removeLoader() {
|
|
284
237
|
this.classList.remove('loading');
|
|
285
|
-
|
|
286
238
|
const btn = this.querySelector('button[type="submit"]');
|
|
287
|
-
|
|
288
|
-
if (btn) {
|
|
289
|
-
setTimeout(() => {
|
|
290
|
-
btn.disabled = false;
|
|
291
|
-
}, 500);
|
|
292
|
-
}
|
|
239
|
+
setTimeout(() => btn.disabled = false, 500);
|
|
293
240
|
}
|
|
294
241
|
|
|
295
242
|
showSuccess({ email, phone }) {
|
|
@@ -331,9 +278,6 @@ class GetInTouchForm extends GHComponent {
|
|
|
331
278
|
|
|
332
279
|
hideFail() {
|
|
333
280
|
const el = this.querySelector('.overflow.fail');
|
|
334
|
-
|
|
335
|
-
if (!el) return;
|
|
336
|
-
|
|
337
281
|
el.style.opacity = 0;
|
|
338
282
|
|
|
339
283
|
setTimeout(() => {
|
|
@@ -371,4 +315,4 @@ class GetInTouchForm extends GHComponent {
|
|
|
371
315
|
|
|
372
316
|
if (!customElements.get('get-in-touch-form')) {
|
|
373
317
|
customElements.define('get-in-touch-form', GetInTouchForm);
|
|
374
|
-
}
|
|
318
|
+
}
|
|
@@ -271,12 +271,6 @@ get-in-touch-form {
|
|
|
271
271
|
}
|
|
272
272
|
}
|
|
273
273
|
}
|
|
274
|
-
.recaptcha-notice {
|
|
275
|
-
margin-top: 10px;
|
|
276
|
-
font-size: 12px;
|
|
277
|
-
line-height: 1.4;
|
|
278
|
-
opacity: 0.7;
|
|
279
|
-
}
|
|
280
274
|
&[data-in-popup] {
|
|
281
275
|
.get-in-touch-form {
|
|
282
276
|
padding-top: 0;
|
|
@@ -374,10 +368,4 @@ popup-container[data-position="bottom-right"] {
|
|
|
374
368
|
}
|
|
375
369
|
}
|
|
376
370
|
}
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
body .grecaptcha-badge {
|
|
380
|
-
visibility: hidden !important;
|
|
381
|
-
opacity: 0 !important;
|
|
382
|
-
pointer-events: none !important;
|
|
383
371
|
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { drawImageCover } from '../utils/draw-image-cover.js';
|
|
2
|
+
|
|
3
|
+
export default function cascadeStrips({
|
|
4
|
+
ctx,
|
|
5
|
+
canvas,
|
|
6
|
+
images,
|
|
7
|
+
progress,
|
|
8
|
+
}) {
|
|
9
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
10
|
+
|
|
11
|
+
// BASE IMAGE
|
|
12
|
+
drawImageCover({
|
|
13
|
+
ctx,
|
|
14
|
+
canvas,
|
|
15
|
+
image: images[0],
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const slidesCount =
|
|
19
|
+
images.length;
|
|
20
|
+
|
|
21
|
+
const sectorWidth =
|
|
22
|
+
canvas.width / slidesCount;
|
|
23
|
+
|
|
24
|
+
// GLOBAL WIPE POSITION
|
|
25
|
+
const totalTravel =
|
|
26
|
+
canvas.width
|
|
27
|
+
+ (
|
|
28
|
+
sectorWidth
|
|
29
|
+
* (slidesCount - 1)
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const wipeX =
|
|
33
|
+
totalTravel * progress;
|
|
34
|
+
|
|
35
|
+
for (let i = 1; i < slidesCount; i++) {
|
|
36
|
+
|
|
37
|
+
const image =
|
|
38
|
+
images[i];
|
|
39
|
+
|
|
40
|
+
// OFFSET BETWEEN WIPES
|
|
41
|
+
const offset =
|
|
42
|
+
sectorWidth * (i - 1);
|
|
43
|
+
|
|
44
|
+
// CURRENT WIPE POSITION
|
|
45
|
+
const currentX =
|
|
46
|
+
wipeX - offset;
|
|
47
|
+
|
|
48
|
+
// NOT YET VISIBLE
|
|
49
|
+
if (currentX <= 0) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
ctx.save();
|
|
54
|
+
|
|
55
|
+
ctx.beginPath();
|
|
56
|
+
|
|
57
|
+
// LAST SLIDE
|
|
58
|
+
if (i === slidesCount - 1) {
|
|
59
|
+
|
|
60
|
+
// CONTINUES TO FULLSCREEN
|
|
61
|
+
ctx.rect(
|
|
62
|
+
0,
|
|
63
|
+
0,
|
|
64
|
+
currentX,
|
|
65
|
+
canvas.height
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
} else {
|
|
69
|
+
|
|
70
|
+
// NORMAL STRIP
|
|
71
|
+
ctx.rect(
|
|
72
|
+
currentX - sectorWidth,
|
|
73
|
+
0,
|
|
74
|
+
sectorWidth,
|
|
75
|
+
canvas.height
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
ctx.clip();
|
|
80
|
+
|
|
81
|
+
drawImageCover({
|
|
82
|
+
ctx,
|
|
83
|
+
canvas,
|
|
84
|
+
image,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
ctx.restore();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { drawImageCover } from '../utils/draw-image-cover.js';
|
|
2
|
+
|
|
3
|
+
export default function fade({
|
|
4
|
+
ctx,
|
|
5
|
+
canvas,
|
|
6
|
+
currentImage,
|
|
7
|
+
nextImage,
|
|
8
|
+
progress,
|
|
9
|
+
}) {
|
|
10
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
11
|
+
|
|
12
|
+
// CURRENT IMAGE
|
|
13
|
+
drawImageCover({
|
|
14
|
+
ctx,
|
|
15
|
+
canvas,
|
|
16
|
+
image: currentImage,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// NEXT IMAGE
|
|
20
|
+
ctx.save();
|
|
21
|
+
|
|
22
|
+
ctx.globalAlpha = progress;
|
|
23
|
+
|
|
24
|
+
drawImageCover({
|
|
25
|
+
ctx,
|
|
26
|
+
canvas,
|
|
27
|
+
image: nextImage,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
ctx.restore();
|
|
31
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { drawImageCover } from '../utils/draw-image-cover.js';
|
|
2
|
+
|
|
3
|
+
export default function multiStrip({
|
|
4
|
+
ctx,
|
|
5
|
+
canvas,
|
|
6
|
+
currentImage,
|
|
7
|
+
nextImage,
|
|
8
|
+
progress,
|
|
9
|
+
isTransitionPhase,
|
|
10
|
+
}) {
|
|
11
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
12
|
+
|
|
13
|
+
// CURRENT IMAGE
|
|
14
|
+
drawImageCover({
|
|
15
|
+
ctx,
|
|
16
|
+
canvas,
|
|
17
|
+
image: currentImage,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (!isTransitionPhase) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const strips = 8;
|
|
25
|
+
|
|
26
|
+
const stripWidth = canvas.width / strips;
|
|
27
|
+
|
|
28
|
+
const stagger = 0.08;
|
|
29
|
+
|
|
30
|
+
for (let i = 0; i < strips; i++) {
|
|
31
|
+
|
|
32
|
+
const delay = i * stagger;
|
|
33
|
+
|
|
34
|
+
let localProgress =
|
|
35
|
+
(progress - delay) / (1 - (stagger * strips));
|
|
36
|
+
|
|
37
|
+
localProgress = Math.max(0, Math.min(1, localProgress));
|
|
38
|
+
|
|
39
|
+
const revealWidth =
|
|
40
|
+
stripWidth * localProgress;
|
|
41
|
+
|
|
42
|
+
const x =
|
|
43
|
+
i * stripWidth;
|
|
44
|
+
|
|
45
|
+
ctx.save();
|
|
46
|
+
|
|
47
|
+
ctx.beginPath();
|
|
48
|
+
|
|
49
|
+
ctx.rect(
|
|
50
|
+
x,
|
|
51
|
+
0,
|
|
52
|
+
revealWidth,
|
|
53
|
+
canvas.height
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
ctx.clip();
|
|
57
|
+
|
|
58
|
+
drawImageCover({
|
|
59
|
+
ctx,
|
|
60
|
+
canvas,
|
|
61
|
+
image: nextImage,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
ctx.restore();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { drawImageCover } from '../utils/draw-image-cover.js';
|
|
2
|
+
|
|
3
|
+
export default function sectors({
|
|
4
|
+
ctx,
|
|
5
|
+
canvas,
|
|
6
|
+
images,
|
|
7
|
+
progress,
|
|
8
|
+
}) {
|
|
9
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
10
|
+
|
|
11
|
+
const slidesCount =
|
|
12
|
+
images.length;
|
|
13
|
+
|
|
14
|
+
const sectorWidth =
|
|
15
|
+
canvas.width / slidesCount;
|
|
16
|
+
|
|
17
|
+
// BASE SLIDE
|
|
18
|
+
drawImageCover({
|
|
19
|
+
ctx,
|
|
20
|
+
canvas,
|
|
21
|
+
image: images[0],
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
for (let i = 1; i < slidesCount; i++) {
|
|
25
|
+
|
|
26
|
+
// START OF THIS SECTOR
|
|
27
|
+
const start =
|
|
28
|
+
i / slidesCount;
|
|
29
|
+
|
|
30
|
+
// END OF THIS SECTOR
|
|
31
|
+
const end =
|
|
32
|
+
(i + 1) / slidesCount;
|
|
33
|
+
|
|
34
|
+
// LOCAL PROGRESS
|
|
35
|
+
let localProgress =
|
|
36
|
+
(progress - start)
|
|
37
|
+
/ (end - start);
|
|
38
|
+
|
|
39
|
+
localProgress =
|
|
40
|
+
Math.max(
|
|
41
|
+
0,
|
|
42
|
+
Math.min(1, localProgress)
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// NOT STARTED
|
|
46
|
+
if (localProgress <= 0) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const x =
|
|
51
|
+
i * sectorWidth;
|
|
52
|
+
|
|
53
|
+
ctx.save();
|
|
54
|
+
|
|
55
|
+
ctx.beginPath();
|
|
56
|
+
|
|
57
|
+
// LAST SECTOR
|
|
58
|
+
if (i === slidesCount - 1) {
|
|
59
|
+
|
|
60
|
+
const expandProgress =
|
|
61
|
+
Math.max(
|
|
62
|
+
0,
|
|
63
|
+
(progress - 0.875) / 0.125
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
const dynamicWidth =
|
|
67
|
+
sectorWidth
|
|
68
|
+
+ (
|
|
69
|
+
(canvas.width - sectorWidth)
|
|
70
|
+
* expandProgress
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
ctx.rect(
|
|
74
|
+
x,
|
|
75
|
+
0,
|
|
76
|
+
dynamicWidth,
|
|
77
|
+
canvas.height
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
} else {
|
|
81
|
+
|
|
82
|
+
ctx.rect(
|
|
83
|
+
x,
|
|
84
|
+
0,
|
|
85
|
+
sectorWidth,
|
|
86
|
+
canvas.height
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
ctx.clip();
|
|
91
|
+
|
|
92
|
+
// FADE-IN
|
|
93
|
+
ctx.globalAlpha =
|
|
94
|
+
localProgress;
|
|
95
|
+
|
|
96
|
+
drawImageCover({
|
|
97
|
+
ctx,
|
|
98
|
+
canvas,
|
|
99
|
+
image: images[i],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
ctx.restore();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { drawImageCover } from '../utils/draw-image-cover.js';
|
|
2
|
+
import { easeInOutCubic } from '../utils/easing.js';
|
|
3
|
+
|
|
4
|
+
export default function wipeLeft({
|
|
5
|
+
ctx,
|
|
6
|
+
canvas,
|
|
7
|
+
currentImage,
|
|
8
|
+
nextImage,
|
|
9
|
+
progress,
|
|
10
|
+
}) {
|
|
11
|
+
const eased = easeInOutCubic(progress);
|
|
12
|
+
|
|
13
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
14
|
+
|
|
15
|
+
drawImageCover({
|
|
16
|
+
ctx,
|
|
17
|
+
canvas,
|
|
18
|
+
image: currentImage,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const revealWidth = canvas.width * eased;
|
|
22
|
+
|
|
23
|
+
ctx.save();
|
|
24
|
+
|
|
25
|
+
ctx.beginPath();
|
|
26
|
+
|
|
27
|
+
ctx.rect(
|
|
28
|
+
0,
|
|
29
|
+
0,
|
|
30
|
+
revealWidth,
|
|
31
|
+
canvas.height
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
ctx.clip();
|
|
35
|
+
|
|
36
|
+
drawImageCover({
|
|
37
|
+
ctx,
|
|
38
|
+
canvas,
|
|
39
|
+
image: nextImage,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
ctx.restore();
|
|
43
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { drawImageCover } from '../utils/draw-image-cover.js';
|
|
2
|
+
|
|
3
|
+
export default function zoom({
|
|
4
|
+
ctx,
|
|
5
|
+
canvas,
|
|
6
|
+
currentImage,
|
|
7
|
+
nextImage,
|
|
8
|
+
progress,
|
|
9
|
+
isTransitionPhase,
|
|
10
|
+
}) {
|
|
11
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
12
|
+
|
|
13
|
+
if (!isTransitionPhase) {
|
|
14
|
+
drawImageCover({
|
|
15
|
+
ctx,
|
|
16
|
+
canvas,
|
|
17
|
+
image: currentImage,
|
|
18
|
+
scale: 1.05,
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// CURRENT IMAGE
|
|
25
|
+
ctx.save();
|
|
26
|
+
|
|
27
|
+
ctx.globalAlpha = 1 - progress;
|
|
28
|
+
|
|
29
|
+
drawImageCover({
|
|
30
|
+
ctx,
|
|
31
|
+
canvas,
|
|
32
|
+
image: currentImage,
|
|
33
|
+
scale: 1 + (progress * 0.1),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
ctx.restore();
|
|
37
|
+
|
|
38
|
+
// NEXT IMAGE
|
|
39
|
+
ctx.save();
|
|
40
|
+
|
|
41
|
+
ctx.globalAlpha = progress;
|
|
42
|
+
|
|
43
|
+
drawImageCover({
|
|
44
|
+
ctx,
|
|
45
|
+
canvas,
|
|
46
|
+
image: nextImage,
|
|
47
|
+
scale: 1.1 - (progress * 0.1),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
ctx.restore();
|
|
51
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function drawImageCover({
|
|
2
|
+
ctx,
|
|
3
|
+
canvas,
|
|
4
|
+
image,
|
|
5
|
+
scale = 1,
|
|
6
|
+
offsetX = 0,
|
|
7
|
+
offsetY = 0,
|
|
8
|
+
}) {
|
|
9
|
+
const canvasRatio = canvas.width / canvas.height;
|
|
10
|
+
const imageRatio = image.width / image.height;
|
|
11
|
+
|
|
12
|
+
let drawWidth;
|
|
13
|
+
let drawHeight;
|
|
14
|
+
|
|
15
|
+
if (imageRatio > canvasRatio) {
|
|
16
|
+
drawHeight = canvas.height * scale;
|
|
17
|
+
drawWidth = drawHeight * imageRatio;
|
|
18
|
+
} else {
|
|
19
|
+
drawWidth = canvas.width * scale;
|
|
20
|
+
drawHeight = drawWidth / imageRatio;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const x = ((canvas.width - drawWidth) / 2) + offsetX;
|
|
24
|
+
const y = ((canvas.height - drawHeight) / 2) + offsetY;
|
|
25
|
+
|
|
26
|
+
ctx.drawImage(image, x, y, drawWidth, drawHeight);
|
|
27
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import html from './video-slider.html';
|
|
2
|
+
import './video-slider.scss';
|
|
3
|
+
|
|
4
|
+
import wipeLeft from './animations/wipe-left.js';
|
|
5
|
+
import fade from './animations/fade.js';
|
|
6
|
+
import zoom from './animations/zoom.js';
|
|
7
|
+
import multiStrip from './animations/multi-strip.js';
|
|
8
|
+
import cascadeStrips from './animations/cascade-strips.js';
|
|
9
|
+
import sectors from './animations/sectors.js';
|
|
10
|
+
|
|
11
|
+
class VideoSlider extends GHComponent {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
|
|
15
|
+
this.animations = {
|
|
16
|
+
wipeLeft,
|
|
17
|
+
fade,
|
|
18
|
+
zoom,
|
|
19
|
+
multiStrip,
|
|
20
|
+
cascadeStrips,
|
|
21
|
+
sectors
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async onServerRender() {
|
|
26
|
+
|
|
27
|
+
this.ghId = this.getAttribute('data-gh-id') || null;
|
|
28
|
+
this.chapter = this.getAttribute('data-chapter') || 'pages';
|
|
29
|
+
this.json = this.ghId
|
|
30
|
+
? await super.getGhData(this.ghId, this.chapter)
|
|
31
|
+
: console.error('data-gh-id attribute is required for video-slider component');
|
|
32
|
+
|
|
33
|
+
this.setAttribute(
|
|
34
|
+
'data-animation',
|
|
35
|
+
this.json.animation
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
this.setAttribute(
|
|
39
|
+
'data-slide-duration',
|
|
40
|
+
this.json.slideDuration
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
this.setAttribute(
|
|
44
|
+
'data-transition-duration',
|
|
45
|
+
this.json.transitionDuration
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
this.setAttribute(
|
|
49
|
+
'data-images',
|
|
50
|
+
JSON.stringify(this.json.images)
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
super.render(html);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async onClientReady() {
|
|
57
|
+
|
|
58
|
+
this.config = {
|
|
59
|
+
animation: this.getAttribute('data-animation'),
|
|
60
|
+
|
|
61
|
+
slideDuration:
|
|
62
|
+
Number(this.getAttribute('data-slide-duration')),
|
|
63
|
+
|
|
64
|
+
transitionDuration:
|
|
65
|
+
Number(this.getAttribute('data-transition-duration'))
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
this.images = JSON.parse(this.getAttribute('data-images'));
|
|
69
|
+
|
|
70
|
+
this.canvas = this.querySelector('canvas');
|
|
71
|
+
|
|
72
|
+
this.ctx = this.canvas.getContext('2d');
|
|
73
|
+
|
|
74
|
+
this.canvas.width = 1280;
|
|
75
|
+
this.canvas.height = 720;
|
|
76
|
+
|
|
77
|
+
this.loadedImages = await this.preloadImages();
|
|
78
|
+
|
|
79
|
+
this.startLoop();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
preloadImages() {
|
|
83
|
+
return Promise.all(
|
|
84
|
+
this.images.map((url) => {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
|
|
87
|
+
const image = new Image();
|
|
88
|
+
|
|
89
|
+
image.crossOrigin = 'anonymous';
|
|
90
|
+
|
|
91
|
+
image.onload = () => {
|
|
92
|
+
resolve(image);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
image.onerror = () => reject(url);
|
|
96
|
+
|
|
97
|
+
image.src = url;
|
|
98
|
+
});
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
startLoop() {
|
|
104
|
+
this.startTime = performance.now();
|
|
105
|
+
|
|
106
|
+
const render = (time) => {
|
|
107
|
+
const elapsed = time - this.startTime;
|
|
108
|
+
|
|
109
|
+
const totalSlides = this.loadedImages.length;
|
|
110
|
+
|
|
111
|
+
const singleSlideTotal =
|
|
112
|
+
this.config.slideDuration + this.config.transitionDuration;
|
|
113
|
+
|
|
114
|
+
const totalDuration =
|
|
115
|
+
singleSlideTotal * totalSlides;
|
|
116
|
+
|
|
117
|
+
const timelineTime =
|
|
118
|
+
elapsed % totalDuration;
|
|
119
|
+
|
|
120
|
+
const currentSlide =
|
|
121
|
+
Math.floor(timelineTime / singleSlideTotal);
|
|
122
|
+
|
|
123
|
+
const nextSlide =
|
|
124
|
+
(currentSlide + 1) % totalSlides;
|
|
125
|
+
|
|
126
|
+
const localTime =
|
|
127
|
+
timelineTime % singleSlideTotal;
|
|
128
|
+
|
|
129
|
+
const isTransitionPhase =
|
|
130
|
+
localTime >= this.config.slideDuration;
|
|
131
|
+
|
|
132
|
+
const transitionTime =
|
|
133
|
+
localTime - this.config.slideDuration;
|
|
134
|
+
|
|
135
|
+
const progress = isTransitionPhase
|
|
136
|
+
? transitionTime / this.config.transitionDuration
|
|
137
|
+
: 0;
|
|
138
|
+
|
|
139
|
+
this.renderFrame({
|
|
140
|
+
currentImage: this.loadedImages[currentSlide],
|
|
141
|
+
nextImage: this.loadedImages[nextSlide],
|
|
142
|
+
progress,
|
|
143
|
+
isTransitionPhase,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
requestAnimationFrame(render);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
requestAnimationFrame(render);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
renderFrame({
|
|
153
|
+
currentImage,
|
|
154
|
+
nextImage,
|
|
155
|
+
progress,
|
|
156
|
+
isTransitionPhase,
|
|
157
|
+
}) {
|
|
158
|
+
if (!currentImage) return;
|
|
159
|
+
|
|
160
|
+
const animation =
|
|
161
|
+
this.animations[this.config.animation];
|
|
162
|
+
|
|
163
|
+
if (!animation) return;
|
|
164
|
+
|
|
165
|
+
animation({
|
|
166
|
+
ctx: this.ctx,
|
|
167
|
+
canvas: this.canvas,
|
|
168
|
+
currentImage,
|
|
169
|
+
nextImage,
|
|
170
|
+
progress,
|
|
171
|
+
isTransitionPhase,
|
|
172
|
+
images: this.loadedImages,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
window.customElements.define('video-slider', VideoSlider);
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# Video Slider Component
|
|
2
|
+
|
|
3
|
+
## Description
|
|
4
|
+
|
|
5
|
+
`video-slider` is a canvas-based animated image slider that supports multiple transition effects between slides.
|
|
6
|
+
|
|
7
|
+
The component loads configuration and images from GudHub data and automatically starts the animation loop after initialization.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Data Structure
|
|
12
|
+
|
|
13
|
+
The component expects the following JSON structure:
|
|
14
|
+
|
|
15
|
+
```json
|
|
16
|
+
{
|
|
17
|
+
"video-slider": {
|
|
18
|
+
"animation": "fade",
|
|
19
|
+
"slideDuration": "100",
|
|
20
|
+
"transitionDuration": "2000",
|
|
21
|
+
"images": [
|
|
22
|
+
"https://app.gudhub.com/userdata/12345/image1.jpg",
|
|
23
|
+
"https://app.gudhub.com/userdata/12354/image2.jpg",
|
|
24
|
+
"https://app.gudhub.com/userdata/11345/image3.jpg"
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Configuration Parameters
|
|
31
|
+
|
|
32
|
+
| Property | Type | Required | Description |
|
|
33
|
+
| ------------------ | ------------- | -------- | ------------------------------------------------------ |
|
|
34
|
+
| animation | string | Yes | Animation type used between slides |
|
|
35
|
+
| slideDuration | string/number | Yes | Duration of static slide display in milliseconds |
|
|
36
|
+
| transitionDuration | string/number | Yes | Duration of slide transition animation in milliseconds |
|
|
37
|
+
| images | array | Yes | Ordered list of image URLs |
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## Supported Animations
|
|
42
|
+
|
|
43
|
+
The following animation values are supported:
|
|
44
|
+
|
|
45
|
+
```text
|
|
46
|
+
wipeLeft
|
|
47
|
+
fade
|
|
48
|
+
zoom
|
|
49
|
+
multiStrip
|
|
50
|
+
cascadeStrips
|
|
51
|
+
sectors
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Example:
|
|
55
|
+
|
|
56
|
+
```json
|
|
57
|
+
{
|
|
58
|
+
"animation": "zoom"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Slide Duration
|
|
65
|
+
|
|
66
|
+
Defines how long a slide remains visible before transition starts.
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
|
|
70
|
+
```json
|
|
71
|
+
{
|
|
72
|
+
"slideDuration": "3000"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Result:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
3 seconds display
|
|
80
|
+
+
|
|
81
|
+
transition animation
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Transition Duration
|
|
87
|
+
|
|
88
|
+
Defines how long the transition animation lasts.
|
|
89
|
+
|
|
90
|
+
Example:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"transitionDuration": "1500"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Result:
|
|
99
|
+
|
|
100
|
+
```text
|
|
101
|
+
1.5 second transition
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Images
|
|
107
|
+
|
|
108
|
+
Images must be provided as an ordered array of URLs.
|
|
109
|
+
|
|
110
|
+
The component displays images in the exact order they appear in the array.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
|
|
114
|
+
```json
|
|
115
|
+
{
|
|
116
|
+
"images": [
|
|
117
|
+
"image-1.jpg",
|
|
118
|
+
"image-2.jpg",
|
|
119
|
+
"image-3.jpg"
|
|
120
|
+
]
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Display order:
|
|
125
|
+
|
|
126
|
+
```text
|
|
127
|
+
image-1
|
|
128
|
+
↓
|
|
129
|
+
image-2
|
|
130
|
+
↓
|
|
131
|
+
image-3
|
|
132
|
+
↓
|
|
133
|
+
image-1
|
|
134
|
+
(repeat)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
# Usage
|
|
140
|
+
|
|
141
|
+
## Static Pages
|
|
142
|
+
|
|
143
|
+
For static pages only `data-gh-id` is required.
|
|
144
|
+
|
|
145
|
+
```html
|
|
146
|
+
<video-slider
|
|
147
|
+
data-gh-id="video-slider"
|
|
148
|
+
></video-slider>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Dynamic Pages
|
|
154
|
+
|
|
155
|
+
For dynamic pages an additional `data-chapter` attribute must be specified.
|
|
156
|
+
|
|
157
|
+
```html
|
|
158
|
+
<video-slider
|
|
159
|
+
data-gh-id="video-slider"
|
|
160
|
+
data-chapter="areas"
|
|
161
|
+
></video-slider>
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### data-chapter
|
|
165
|
+
|
|
166
|
+
Defines the chapter from which GudHub should load dynamic page data.
|
|
167
|
+
|
|
168
|
+
Example:
|
|
169
|
+
|
|
170
|
+
```html
|
|
171
|
+
data-chapter="areas"
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
# Required Attributes
|
|
177
|
+
|
|
178
|
+
| Attribute | Required | Description |
|
|
179
|
+
| ------------ | ------------------ | ------------------------------------------ |
|
|
180
|
+
| data-gh-id | Yes | GudHub component identifier |
|
|
181
|
+
| data-chapter | Dynamic pages only | Chapter used to retrieve current item data |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
# Example
|
|
186
|
+
|
|
187
|
+
```html
|
|
188
|
+
<video-slider
|
|
189
|
+
data-gh-id="video-slider"
|
|
190
|
+
data-chapter="areas"
|
|
191
|
+
></video-slider>
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```json
|
|
195
|
+
{
|
|
196
|
+
"video-slider": {
|
|
197
|
+
"animation": "fade",
|
|
198
|
+
"slideDuration": "3000",
|
|
199
|
+
"transitionDuration": "1500",
|
|
200
|
+
"images": [
|
|
201
|
+
"https://example.com/slide-1.jpg",
|
|
202
|
+
"https://example.com/slide-2.jpg",
|
|
203
|
+
"https://example.com/slide-3.jpg"
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
# Notes
|
|
212
|
+
|
|
213
|
+
* All images should be publicly accessible URLs.
|
|
214
|
+
* Images are preloaded before animation starts.
|
|
215
|
+
* The slider automatically loops through all images.
|
|
216
|
+
* Images are displayed using a cover-fit algorithm similar to CSS `object-fit: cover`.
|
|
217
|
+
* Animation starts automatically after component initialization.
|
|
218
|
+
* If an invalid animation name is provided, no transition animation will be rendered.
|
package/src/config.js
CHANGED
|
@@ -67,9 +67,8 @@ export { GoogleAnalytics } from './components/google-analytics/config.js';
|
|
|
67
67
|
export { GoogleTag } from './components/google-tag/config.js';
|
|
68
68
|
export { cookiesPopup } from './components/cookies-popup/config.js';
|
|
69
69
|
export { infoTable } from './components/info-table/config.js';
|
|
70
|
-
export { liqpayPaymentButton } from './components/liqpay-payment-button/config.js';
|
|
71
|
-
export { liqpayComponent } from './components/liqpay-component/config.js';
|
|
72
70
|
export { faqComponent } from './components/faq-component/config.js';
|
|
71
|
+
export { videoSlider } from './components/video-slider/config.js';
|
|
73
72
|
|
|
74
73
|
// export { liqPayComponent } from './components/liqpay-component/config.js';
|
|
75
74
|
// export { htmlTextFromEditorJs } from './components/html-text-from-editor-js/config.js';
|