create-fesd-app 1.0.46 → 1.0.48
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 +7 -3
- package/src/assets/css/layouts/_emblaStyle.sass +100 -0
- package/src/assets/css/layouts/_pluginExtra.sass +2 -0
- package/src/assets/css/layouts/article4/_article4_setting.sass +7 -6
- package/src/assets/css/pages/index.sass +50 -0
- package/src/assets/js/apps/index/page.js +41 -20
- package/src/assets/js/plugins/embla.plugin.js +603 -0
- package/src/assets/js/plugins/index.js +1 -0
- package/src/assets/js/plugins/swiperV11.plugin.js +275 -0
- package/src/layouts/_template.pug +2 -0
- package/src/pages/index.pug +55 -67
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-fesd-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.48",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-fesd-app": "bin/create-fesd-app.mjs"
|
|
@@ -19,11 +19,15 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@tailwindcss/postcss": "^4.1.10",
|
|
21
21
|
"@tailwindcss/vite": "^4.1.10",
|
|
22
|
-
"@xwadex/fesd": "0.0.
|
|
22
|
+
"@xwadex/fesd": "0.0.70",
|
|
23
23
|
"ansi-colors": "^4.1.3",
|
|
24
24
|
"chalk": "^5.3.0",
|
|
25
25
|
"clsx": "^2.1.1",
|
|
26
26
|
"colorthief": "^2.4.0",
|
|
27
|
+
"embla-carousel": "^8.6.0",
|
|
28
|
+
"embla-carousel-autoplay": "^8.6.0",
|
|
29
|
+
"embla-carousel-class-names": "^8.6.0",
|
|
30
|
+
"embla-carousel-fade": "^8.6.0",
|
|
27
31
|
"commander": "^12.1.0",
|
|
28
32
|
"flatpickr": "^4.6.13",
|
|
29
33
|
"gsap": "^3.12.5",
|
|
@@ -47,7 +51,7 @@
|
|
|
47
51
|
"@vituum/vite-plugin-pug": "^1.1.0",
|
|
48
52
|
"sass": "1.77.4",
|
|
49
53
|
"sass-migrator": "^2.1.0",
|
|
50
|
-
"vite": "
|
|
54
|
+
"vite": "7.3.3",
|
|
51
55
|
"vite-plugin-compression": "^0.5.1",
|
|
52
56
|
"vite-plugin-imagemin": "^0.6.1"
|
|
53
57
|
},
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// 固定不改 可以用變數條整
|
|
2
|
+
// --slide-spacing: 0px
|
|
3
|
+
.embla
|
|
4
|
+
--slide-size: 30%
|
|
5
|
+
position: relative
|
|
6
|
+
overflow: hidden
|
|
7
|
+
width: 100%
|
|
8
|
+
.embla__viewport
|
|
9
|
+
overflow: hidden
|
|
10
|
+
width: 100%
|
|
11
|
+
.embla__container
|
|
12
|
+
display: flex
|
|
13
|
+
touch-action: pan-y pinch-zoom
|
|
14
|
+
margin-left: calc(var(--slide-spacing, 0) * -1)
|
|
15
|
+
.embla__slide
|
|
16
|
+
transform: translate3d(0, 0, 0)
|
|
17
|
+
flex: 0 0 var(--slide-size, 100%)
|
|
18
|
+
min-width: 0
|
|
19
|
+
padding-left: var(--slide-spacing, 0)
|
|
20
|
+
overflow: hidden
|
|
21
|
+
// no scroll
|
|
22
|
+
&.embla--noScrollable
|
|
23
|
+
.embla__container
|
|
24
|
+
transform: none !important
|
|
25
|
+
.embla__prev,
|
|
26
|
+
.embla__next,
|
|
27
|
+
.embla__dots
|
|
28
|
+
display: none !important
|
|
29
|
+
// parallax
|
|
30
|
+
.embla__parallax
|
|
31
|
+
height: 100%
|
|
32
|
+
overflow: hidden
|
|
33
|
+
.embla__parallax__layer
|
|
34
|
+
position: relative
|
|
35
|
+
height: 100%
|
|
36
|
+
width: 100%
|
|
37
|
+
display: flex
|
|
38
|
+
justify-content: center
|
|
39
|
+
.embla__parallax__img
|
|
40
|
+
max-width: none
|
|
41
|
+
flex: 0 0 calc(115% + (var(--slide-spacing, 0) * 2))
|
|
42
|
+
object-fit: cover
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
// 箭頭控制
|
|
46
|
+
.embla__controls
|
|
47
|
+
display: flex
|
|
48
|
+
justify-content: center
|
|
49
|
+
gap: 2.5rem
|
|
50
|
+
pointer-events: none
|
|
51
|
+
.embla__prev
|
|
52
|
+
>i
|
|
53
|
+
display: block
|
|
54
|
+
transform: rotate(180deg)
|
|
55
|
+
.embla__prev,
|
|
56
|
+
.embla__next
|
|
57
|
+
font-size: px(18)
|
|
58
|
+
pointer-events: auto
|
|
59
|
+
&[isHide]
|
|
60
|
+
opacity: 0.2
|
|
61
|
+
cursor: default
|
|
62
|
+
pointer-events: none
|
|
63
|
+
|
|
64
|
+
// 點點控制
|
|
65
|
+
.embla__dots
|
|
66
|
+
--bullet-size: 5px
|
|
67
|
+
--bullet-gap: 10px
|
|
68
|
+
--bullet-color-RGB: 0,0,0
|
|
69
|
+
|
|
70
|
+
display: flex
|
|
71
|
+
justify-content: center
|
|
72
|
+
align-items: center
|
|
73
|
+
gap: var(--bullet-gap)
|
|
74
|
+
.embla__dot
|
|
75
|
+
width: var(--bullet-size)
|
|
76
|
+
height: var(--bullet-size)
|
|
77
|
+
padding: 0
|
|
78
|
+
border-radius: 50%
|
|
79
|
+
background: rgba(var(--bullet-color-RGB), 0)
|
|
80
|
+
border: 1px solid rgba(var(--bullet-color-RGB), 1)
|
|
81
|
+
&.is-selected
|
|
82
|
+
background: rgba(var(--bullet-color-RGB),1)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
// 滾動條
|
|
86
|
+
.embla__progress
|
|
87
|
+
border-radius: 1.875rem
|
|
88
|
+
background-color: rgba(0,0,0,0.1)
|
|
89
|
+
position: relative
|
|
90
|
+
height: 0.625rem
|
|
91
|
+
width: 200px
|
|
92
|
+
max-width: 90%
|
|
93
|
+
overflow: hidden
|
|
94
|
+
.embla__progressBar
|
|
95
|
+
background-color: rgba(0,0,0,.5)
|
|
96
|
+
height: 100%
|
|
97
|
+
width: 100%
|
|
98
|
+
transform: scaleX(0)
|
|
99
|
+
transform-origin: left
|
|
100
|
+
transition: transform 0.3s ease
|
|
@@ -288,16 +288,17 @@
|
|
|
288
288
|
// 以下可修改
|
|
289
289
|
font-size: 16px
|
|
290
290
|
line-height: 1.2
|
|
291
|
-
font-weight:
|
|
291
|
+
font-weight: 700
|
|
292
292
|
tbody
|
|
293
293
|
td
|
|
294
|
-
&.lefthead
|
|
295
|
-
font-size: 16px
|
|
296
|
-
line-height: 1.2
|
|
297
|
-
font-weight: 600
|
|
298
294
|
// 以下可修改
|
|
299
295
|
font-size: 16px
|
|
300
|
-
line-height: 1.
|
|
296
|
+
line-height: 1.6
|
|
297
|
+
font-weight: 400
|
|
298
|
+
letter-spacing: 0.5px
|
|
299
|
+
&.lefthead
|
|
300
|
+
line-height: 1.2
|
|
301
|
+
font-weight: 700
|
|
301
302
|
|
|
302
303
|
%table_tipText
|
|
303
304
|
// 以下可修改
|
|
@@ -291,5 +291,55 @@
|
|
|
291
291
|
justify-content: center
|
|
292
292
|
align-items: center
|
|
293
293
|
cursor: pointer
|
|
294
|
+
|
|
295
|
+
section.section6
|
|
296
|
+
.container
|
|
297
|
+
padding-block: 30px
|
|
298
|
+
// +baseSpace(10)
|
|
299
|
+
// +rwdmax(1200)
|
|
300
|
+
// +baseSpace(1)
|
|
301
|
+
.titleBox
|
|
302
|
+
margin-bottom: px(20)
|
|
303
|
+
.sub-title,
|
|
304
|
+
.main-title-M
|
|
305
|
+
display: block
|
|
306
|
+
.embla-video-demo
|
|
307
|
+
--slide-size: 100%
|
|
308
|
+
.video-demo-card
|
|
309
|
+
position: relative
|
|
310
|
+
overflow: hidden
|
|
311
|
+
border-radius: 5px
|
|
312
|
+
background: #000
|
|
313
|
+
aspect-ratio: 16 / 9
|
|
314
|
+
.video-demo-label
|
|
315
|
+
position: absolute
|
|
316
|
+
top: px(20)
|
|
317
|
+
left: px(20)
|
|
318
|
+
z-index: 2
|
|
319
|
+
padding: px(10) px(14)
|
|
320
|
+
color: #fff
|
|
321
|
+
background: rgba(#000, .65)
|
|
322
|
+
border-radius: 5px
|
|
323
|
+
pointer-events: none
|
|
324
|
+
span,
|
|
325
|
+
small
|
|
326
|
+
display: block
|
|
327
|
+
small
|
|
328
|
+
margin-top: 2px
|
|
329
|
+
opacity: .7
|
|
330
|
+
.mediaBox,
|
|
331
|
+
.youtube-player,
|
|
332
|
+
.youku-player,
|
|
333
|
+
iframe
|
|
334
|
+
width: 100%
|
|
335
|
+
height: 100%
|
|
336
|
+
iframe
|
|
337
|
+
display: block
|
|
338
|
+
border: 0
|
|
339
|
+
video
|
|
340
|
+
width: 100%
|
|
341
|
+
height: 100%
|
|
342
|
+
object-fit: contain
|
|
343
|
+
|
|
294
344
|
[video-cover="off"]
|
|
295
345
|
padding: 0 !important
|
|
@@ -21,25 +21,30 @@ import {
|
|
|
21
21
|
// flatpickr
|
|
22
22
|
import { Mandarin } from "flatpickr/dist/l10n/zh.js"
|
|
23
23
|
// swiper
|
|
24
|
-
import { SwiperVer11, cn } from "@/plugins";
|
|
24
|
+
import { SwiperVer11, SwiperEmbla, cn } from "@/plugins";
|
|
25
25
|
import { bannerConfig } from "@/configs"
|
|
26
|
-
|
|
26
|
+
const {
|
|
27
|
+
Embla,
|
|
28
|
+
setupVideo: setupVideoEmbla,
|
|
29
|
+
setupYt: setupYtEmbla,
|
|
30
|
+
setupYouku: setupYoukuEmbla
|
|
31
|
+
} = SwiperEmbla
|
|
27
32
|
// swiper
|
|
28
|
-
const {
|
|
33
|
+
const {
|
|
34
|
+
SwiperV11,
|
|
35
|
+
Autoplay,
|
|
36
|
+
setupVideo: setupVideoSwiper,
|
|
37
|
+
setupYt: setupYtSwiper,
|
|
38
|
+
setupYouku: setupYoukuSwiper,
|
|
39
|
+
} = SwiperVer11
|
|
29
40
|
const swiperHandler = {};
|
|
30
41
|
|
|
31
42
|
swiperHandler.banner = function () {
|
|
32
43
|
const bannerEvents = {
|
|
33
44
|
...bannerConfig,
|
|
34
45
|
autoplay: false,
|
|
35
|
-
init() {
|
|
36
|
-
console.log('swiper init!!');
|
|
37
|
-
},
|
|
38
|
-
slideChangeTransitionStart(swiper) {
|
|
39
|
-
isVideo(swiper);
|
|
40
|
-
},
|
|
41
46
|
};
|
|
42
|
-
const banner = new SwiperV11('.swiper', bannerEvents);
|
|
47
|
+
const banner = new SwiperV11('.banner .swiper', bannerEvents);
|
|
43
48
|
};
|
|
44
49
|
|
|
45
50
|
swiperHandler.all = function () {
|
|
@@ -125,19 +130,35 @@ const s4Handler = function () {
|
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
const s6Handler = function () {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
133
|
+
const options = {
|
|
134
|
+
modules: [Autoplay],
|
|
135
|
+
speed: 1200,
|
|
136
|
+
autoplay: {
|
|
137
|
+
delay: 5000,
|
|
138
|
+
},
|
|
139
|
+
loop: true,
|
|
140
|
+
slidesPerView: 1,
|
|
135
141
|
on: {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
init(swiper) {
|
|
143
|
+
setupVideoSwiper(swiper);
|
|
144
|
+
setupYtSwiper(swiper, "swiper-video-demo");
|
|
145
|
+
setupYoukuSwiper(swiper);
|
|
146
|
+
},
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const swiperTest = new SwiperV11('.swiper-video-demo', options)
|
|
150
|
+
|
|
151
|
+
const emblaTest = Embla({
|
|
152
|
+
selector: '.embla-video-demo',
|
|
153
|
+
autoplay: true,
|
|
154
|
+
loop: true,
|
|
155
|
+
onInit: (embla) => {
|
|
156
|
+
setupVideoEmbla(embla)
|
|
157
|
+
setupYtEmbla(embla, "embla-video-demo")
|
|
158
|
+
setupYoukuEmbla(embla)
|
|
139
159
|
},
|
|
140
160
|
})
|
|
161
|
+
|
|
141
162
|
};
|
|
142
163
|
|
|
143
164
|
const s8Handler = function () {
|
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import EmblaCarousel from "embla-carousel";
|
|
2
|
+
import Autoplay from "embla-carousel-autoplay";
|
|
3
|
+
import Fade from "embla-carousel-fade";
|
|
4
|
+
import ClassNames from "embla-carousel-class-names";
|
|
5
|
+
|
|
6
|
+
export const Embla = (options) => {
|
|
7
|
+
const {
|
|
8
|
+
selector,
|
|
9
|
+
duration = 40,
|
|
10
|
+
loop = false,
|
|
11
|
+
hasButtons = false,
|
|
12
|
+
hasDots = false,
|
|
13
|
+
dotsDynamic = true,
|
|
14
|
+
dotsClickAble = false,
|
|
15
|
+
autoplay = false,
|
|
16
|
+
autoplayDelay = 6000,
|
|
17
|
+
autoplayStopOnHover = false,
|
|
18
|
+
useFade = false,
|
|
19
|
+
useClassNames = true,
|
|
20
|
+
useProgressBar = false,
|
|
21
|
+
usePageNumber = false,
|
|
22
|
+
classNamesOptions = {
|
|
23
|
+
selected: "is-selected",
|
|
24
|
+
snapped: "is-snapped",
|
|
25
|
+
visible: "is-visible",
|
|
26
|
+
},
|
|
27
|
+
emblaOptions = {},
|
|
28
|
+
onInit = () => { },
|
|
29
|
+
onReInit = () => { },
|
|
30
|
+
onSelect = () => { },
|
|
31
|
+
onDestroy = () => { },
|
|
32
|
+
onPointerDown = () => { },
|
|
33
|
+
onPointerUp = () => { },
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
const core = createEmblaCore({
|
|
37
|
+
selector,
|
|
38
|
+
loop,
|
|
39
|
+
duration,
|
|
40
|
+
useFade,
|
|
41
|
+
useClassNames,
|
|
42
|
+
classNamesOptions,
|
|
43
|
+
autoplay,
|
|
44
|
+
autoplayDelay,
|
|
45
|
+
autoplayStopOnHover,
|
|
46
|
+
emblaOptions,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!core) return null;
|
|
50
|
+
|
|
51
|
+
const { embla, autoplayPlugin, root } = core;
|
|
52
|
+
|
|
53
|
+
function updateScrollableState() {
|
|
54
|
+
const scrollSnaps = embla.scrollSnapList();
|
|
55
|
+
const isScrollable = scrollSnaps.length > 1;
|
|
56
|
+
|
|
57
|
+
root.classList.toggle("embla--noScrollable", !isScrollable);
|
|
58
|
+
if (autoplay && !isScrollable) autoplayPlugin?.stop();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
updateScrollableState();
|
|
62
|
+
embla.on("init", updateScrollableState);
|
|
63
|
+
embla.on("reInit", updateScrollableState);
|
|
64
|
+
|
|
65
|
+
if (hasButtons) addPrevNextBtnsClickHandlers(embla, root, autoplayPlugin);
|
|
66
|
+
if (hasDots)
|
|
67
|
+
addDotBtnsAndClickHandlers(
|
|
68
|
+
embla,
|
|
69
|
+
root,
|
|
70
|
+
autoplayPlugin,
|
|
71
|
+
dotsClickAble,
|
|
72
|
+
dotsDynamic,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
if (useProgressBar) setupProgressBar(embla, root);
|
|
76
|
+
if (usePageNumber) setupPageNumber(embla, root);
|
|
77
|
+
|
|
78
|
+
// 個別定義事件
|
|
79
|
+
if (onInit) embla.on("init", () => onInit(embla));
|
|
80
|
+
if (onReInit) embla.on("reInit", () => onReInit(embla));
|
|
81
|
+
if (onSelect) embla.on("select", () => onSelect(embla));
|
|
82
|
+
if (onDestroy) embla.on("destroy", () => onDestroy(embla));
|
|
83
|
+
if (onPointerDown) embla.on("pointerDown", () => onPointerDown(embla));
|
|
84
|
+
if (onPointerUp) embla.on("pointerUp", () => onPointerUp(embla));
|
|
85
|
+
|
|
86
|
+
return embla;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const createEmblaCore = ({
|
|
90
|
+
selector,
|
|
91
|
+
loop,
|
|
92
|
+
duration,
|
|
93
|
+
autoplay,
|
|
94
|
+
autoplayDelay,
|
|
95
|
+
autoplayStopOnHover,
|
|
96
|
+
useFade,
|
|
97
|
+
useClassNames,
|
|
98
|
+
classNamesOptions,
|
|
99
|
+
emblaOptions,
|
|
100
|
+
}) => {
|
|
101
|
+
const root = document.querySelector(selector);
|
|
102
|
+
if (!root || root.classList.contains("embla--active")) return null;
|
|
103
|
+
|
|
104
|
+
const viewport = root.querySelector(".embla__viewport");
|
|
105
|
+
if (!viewport) return null;
|
|
106
|
+
|
|
107
|
+
const plugins = [];
|
|
108
|
+
const autoplayPlugin = autoplay
|
|
109
|
+
? Autoplay({
|
|
110
|
+
delay: autoplayDelay,
|
|
111
|
+
stopOnInteraction: false,
|
|
112
|
+
stopOnMouseEnter: autoplayStopOnHover,
|
|
113
|
+
})
|
|
114
|
+
: null;
|
|
115
|
+
|
|
116
|
+
if (autoplayPlugin) plugins.push(autoplayPlugin);
|
|
117
|
+
if (useFade) plugins.push(Fade());
|
|
118
|
+
if (useClassNames) plugins.push(ClassNames(classNamesOptions));
|
|
119
|
+
|
|
120
|
+
const embla = EmblaCarousel(
|
|
121
|
+
viewport,
|
|
122
|
+
{ loop, duration, ...emblaOptions },
|
|
123
|
+
plugins,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
root.classList.add("embla--active");
|
|
127
|
+
return { embla, autoplayPlugin, root };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const addPrevNextBtnsClickHandlers = (embla, root, autoplayPlugin) => {
|
|
131
|
+
const prevBtn = root.querySelector(".embla__prev");
|
|
132
|
+
const nextBtn = root.querySelector(".embla__next");
|
|
133
|
+
if (!prevBtn || !nextBtn) return;
|
|
134
|
+
|
|
135
|
+
const scrollPrev = () => {
|
|
136
|
+
embla.scrollPrev();
|
|
137
|
+
autoplayPlugin?.reset();
|
|
138
|
+
};
|
|
139
|
+
const scrollNext = () => {
|
|
140
|
+
embla.scrollNext();
|
|
141
|
+
autoplayPlugin?.reset();
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
prevBtn.addEventListener("click", scrollPrev);
|
|
145
|
+
nextBtn.addEventListener("click", scrollNext);
|
|
146
|
+
|
|
147
|
+
const toggleButtons = () => {
|
|
148
|
+
embla.canScrollPrev()
|
|
149
|
+
? prevBtn.removeAttribute("isHide")
|
|
150
|
+
: prevBtn.setAttribute("isHide", true);
|
|
151
|
+
embla.canScrollNext()
|
|
152
|
+
? nextBtn.removeAttribute("isHide")
|
|
153
|
+
: nextBtn.setAttribute("isHide", true);
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
embla.on("init", toggleButtons);
|
|
157
|
+
embla.on("reInit", toggleButtons);
|
|
158
|
+
embla.on("select", toggleButtons);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// 輪播點點
|
|
162
|
+
const addDotBtnsAndClickHandlers = (
|
|
163
|
+
embla,
|
|
164
|
+
root,
|
|
165
|
+
autoplayPlugin,
|
|
166
|
+
dotsClickAble,
|
|
167
|
+
dotsDynamic,
|
|
168
|
+
) => {
|
|
169
|
+
const dotsContainer = root.querySelector(".embla__dots");
|
|
170
|
+
if (!dotsContainer) return;
|
|
171
|
+
|
|
172
|
+
let track = dotsContainer;
|
|
173
|
+
if (dotsDynamic) {
|
|
174
|
+
const wrapper = dotsContainer.querySelector(".embla__dots__track__wrapper");
|
|
175
|
+
if (wrapper) {
|
|
176
|
+
const dotsTrack = wrapper.querySelector(".embla__dots__track");
|
|
177
|
+
if (dotsTrack) {
|
|
178
|
+
track = dotsTrack;
|
|
179
|
+
} else {
|
|
180
|
+
track = dotsContainer;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (!track) return;
|
|
186
|
+
|
|
187
|
+
const containerStyle = getComputedStyle(dotsContainer);
|
|
188
|
+
const dotsWidth =
|
|
189
|
+
parseFloat(containerStyle.getPropertyValue("--bullet-size")) || 0;
|
|
190
|
+
const maxDots =
|
|
191
|
+
parseInt(containerStyle.getPropertyValue("--max-bullet-count")) || 5;
|
|
192
|
+
const inactiveScale =
|
|
193
|
+
parseFloat(containerStyle.getPropertyValue("--bullet-inactive-scale")) || 1;
|
|
194
|
+
|
|
195
|
+
let dotNodes = [];
|
|
196
|
+
|
|
197
|
+
function dotsBindClick(dot, index) {
|
|
198
|
+
if (!dotsClickAble) return;
|
|
199
|
+
dot.style.cursor = "pointer";
|
|
200
|
+
dot.addEventListener("click", () => {
|
|
201
|
+
embla.scrollTo(index);
|
|
202
|
+
autoplayPlugin?.reset();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const createDots = () => {
|
|
207
|
+
track.innerHTML = "";
|
|
208
|
+
dotNodes = [];
|
|
209
|
+
|
|
210
|
+
dotNodes = embla.scrollSnapList().map((_, index) => {
|
|
211
|
+
const dot = document.createElement("button");
|
|
212
|
+
dot.className = "embla__dot";
|
|
213
|
+
dot.type = "button";
|
|
214
|
+
|
|
215
|
+
dotsBindClick(dot, index);
|
|
216
|
+
track.appendChild(dot);
|
|
217
|
+
return dot;
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const updateDots = () => {
|
|
222
|
+
const selected = embla.selectedScrollSnap();
|
|
223
|
+
// 預設點點
|
|
224
|
+
if (!dotsDynamic) {
|
|
225
|
+
dotNodes.forEach((dot, i) =>
|
|
226
|
+
dot.classList.toggle("is-selected", i === selected),
|
|
227
|
+
);
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 動態點點
|
|
232
|
+
const total = dotNodes.length;
|
|
233
|
+
const half = Math.floor(maxDots / 2);
|
|
234
|
+
|
|
235
|
+
dotNodes.forEach((dot, i) => {
|
|
236
|
+
const distance = Math.abs(i - selected);
|
|
237
|
+
dot.classList.toggle("is-selected", i === selected);
|
|
238
|
+
|
|
239
|
+
const scale = distance === 0 ? 1 : inactiveScale;
|
|
240
|
+
dot.style.transform = `scale(${scale})`;
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
let offset = selected - half;
|
|
244
|
+
if (offset < 0) offset = 0;
|
|
245
|
+
if (offset > total - maxDots) offset = Math.max(total - maxDots, 0);
|
|
246
|
+
|
|
247
|
+
const trackStyle = getComputedStyle(track);
|
|
248
|
+
const gap = parseFloat(trackStyle.gap) || 0;
|
|
249
|
+
|
|
250
|
+
const translate = -(dotsWidth + gap) * offset;
|
|
251
|
+
track.style.transform = `translateX(${translate}px)`;
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
embla.on("init", () => {
|
|
255
|
+
createDots();
|
|
256
|
+
updateDots();
|
|
257
|
+
});
|
|
258
|
+
embla.on("reInit", () => {
|
|
259
|
+
createDots();
|
|
260
|
+
updateDots();
|
|
261
|
+
});
|
|
262
|
+
embla.on("select", updateDots);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const setupPageNumber = (embla, root) => {
|
|
266
|
+
const snapDisplay = root.querySelector(".embla__selected-snap-display");
|
|
267
|
+
if (!snapDisplay) return;
|
|
268
|
+
const updateSnapDisplay = (embla) => {
|
|
269
|
+
const selectedSnap = embla.selectedScrollSnap();
|
|
270
|
+
const snapCount = embla.scrollSnapList().length;
|
|
271
|
+
snapDisplay.innerHTML = `${selectedSnap + 1} / ${snapCount}`;
|
|
272
|
+
};
|
|
273
|
+
embla.on("select", updateSnapDisplay);
|
|
274
|
+
updateSnapDisplay(embla);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const setupProgressBar = (embla, root) => {
|
|
278
|
+
const bar = root.querySelector(".embla__progressBar");
|
|
279
|
+
if (!bar) return;
|
|
280
|
+
|
|
281
|
+
const applyProgress = () => {
|
|
282
|
+
const progress = Math.max(0, Math.min(1, embla.scrollProgress()));
|
|
283
|
+
bar.style.transform = `scaleX(${progress})`;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
embla.on("init", applyProgress);
|
|
287
|
+
embla.on("reInit", applyProgress);
|
|
288
|
+
embla.on("scroll", applyProgress);
|
|
289
|
+
embla.on("select", applyProgress);
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const stopAutoplayNextTick = (autoplayPlugin) => {
|
|
293
|
+
if (!autoplayPlugin) return;
|
|
294
|
+
|
|
295
|
+
autoplayPlugin.stop();
|
|
296
|
+
setTimeout(() => {
|
|
297
|
+
autoplayPlugin.stop();
|
|
298
|
+
}, 100);
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
// 串接
|
|
302
|
+
export const setupThumb = (
|
|
303
|
+
mainEmbla,
|
|
304
|
+
thumbEmbla,
|
|
305
|
+
activeClass = "is-selected",
|
|
306
|
+
) => {
|
|
307
|
+
if (!mainEmbla || !thumbEmbla) return;
|
|
308
|
+
if (thumbEmbla.slideNodes().length !== mainEmbla.slideNodes().length)
|
|
309
|
+
console.warn("[setupThumbSync] slide not match");
|
|
310
|
+
|
|
311
|
+
const addThumbClickHandlers = () => {
|
|
312
|
+
thumbEmbla.slideNodes().forEach((thumb, index) => {
|
|
313
|
+
if (thumb.dataset.emblaThumbClickBound === "true") return;
|
|
314
|
+
|
|
315
|
+
thumb.dataset.emblaThumbClickBound = "true";
|
|
316
|
+
thumb.addEventListener("click", () => {
|
|
317
|
+
mainEmbla.scrollTo(index);
|
|
318
|
+
});
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
const updateThumbSelection = () => {
|
|
322
|
+
const selectedIndex = mainEmbla.selectedScrollSnap();
|
|
323
|
+
thumbEmbla.slideNodes().forEach((thumb, i) => {
|
|
324
|
+
thumb.classList.toggle(activeClass, i === selectedIndex);
|
|
325
|
+
});
|
|
326
|
+
thumbEmbla.scrollTo(selectedIndex);
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
updateThumbSelection();
|
|
330
|
+
addThumbClickHandlers();
|
|
331
|
+
|
|
332
|
+
mainEmbla.on("select", updateThumbSelection);
|
|
333
|
+
thumbEmbla.on("reInit", addThumbClickHandlers);
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// 切換
|
|
337
|
+
export const switchTheme = (
|
|
338
|
+
embla,
|
|
339
|
+
containerSelectors = [],
|
|
340
|
+
selectorWithinSlide = ".slideContent",
|
|
341
|
+
attributeName = "data-theme",
|
|
342
|
+
) => {
|
|
343
|
+
const targets = (
|
|
344
|
+
Array.isArray(containerSelectors)
|
|
345
|
+
? containerSelectors
|
|
346
|
+
: [containerSelectors]
|
|
347
|
+
)
|
|
348
|
+
.map((selector) => document.querySelector(selector))
|
|
349
|
+
.filter(Boolean);
|
|
350
|
+
|
|
351
|
+
const slides = embla.slideNodes();
|
|
352
|
+
if (!targets.length) return;
|
|
353
|
+
|
|
354
|
+
const applyTheme = () => {
|
|
355
|
+
const index = embla.selectedScrollSnap();
|
|
356
|
+
const activeSlide = slides[index];
|
|
357
|
+
const el = activeSlide?.querySelector(selectorWithinSlide);
|
|
358
|
+
const theme = el?.getAttribute(attributeName);
|
|
359
|
+
|
|
360
|
+
if (theme) {
|
|
361
|
+
targets.forEach((target) => {
|
|
362
|
+
target.setAttribute(attributeName, theme);
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
embla.on("select", applyTheme);
|
|
368
|
+
applyTheme();
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
// video
|
|
372
|
+
export const setupVideo = (embla) => {
|
|
373
|
+
const slides = embla.slideNodes();
|
|
374
|
+
const autoplayPlugin = embla.plugins()?.autoplay;
|
|
375
|
+
if (!slides || slides.length === 1 || !autoplayPlugin) return;
|
|
376
|
+
let timer = null;
|
|
377
|
+
|
|
378
|
+
const clearVideoTimer = () => {
|
|
379
|
+
if (!timer) return;
|
|
380
|
+
clearTimeout(timer);
|
|
381
|
+
timer = null;
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
const pauseVideo = () => {
|
|
385
|
+
clearVideoTimer();
|
|
386
|
+
slides.forEach((slide) => {
|
|
387
|
+
const video = slide.querySelector("video");
|
|
388
|
+
if (!video) return;
|
|
389
|
+
video.removeAttribute("loop");
|
|
390
|
+
video.pause();
|
|
391
|
+
video.currentTime = 0;
|
|
392
|
+
video.onended = null;
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
const control = () => {
|
|
396
|
+
pauseVideo();
|
|
397
|
+
const index = embla.selectedScrollSnap();
|
|
398
|
+
const currentSlide = slides[index];
|
|
399
|
+
const video = currentSlide?.querySelector("video");
|
|
400
|
+
if (video) {
|
|
401
|
+
timer = setTimeout(() => {
|
|
402
|
+
if (autoplayPlugin.isPlaying()) autoplayPlugin.stop();
|
|
403
|
+
}, 150);
|
|
404
|
+
video.onended = () => {
|
|
405
|
+
if (embla.autoPlayPause) return;
|
|
406
|
+
embla.canScrollNext() ? embla.scrollNext() : embla.scrollTo(0);
|
|
407
|
+
if (!autoplayPlugin.isPlaying()) autoplayPlugin.play();
|
|
408
|
+
};
|
|
409
|
+
video.play()
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
control();
|
|
413
|
+
embla.on("select", control);
|
|
414
|
+
embla.on("destroy", clearVideoTimer);
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
// yt
|
|
418
|
+
export const setupYt = (embla, ID) => {
|
|
419
|
+
const slides = embla.slideNodes();
|
|
420
|
+
const autoplayPlugin = embla.plugins()?.autoplay;
|
|
421
|
+
let isDestroyed = false;
|
|
422
|
+
if (!slides || slides.length === 1) return;
|
|
423
|
+
|
|
424
|
+
const onYouTubeIframeAPIReady = (playerID, videoID) => {
|
|
425
|
+
if (!window.YT?.ready || !window.YT?.Player) return;
|
|
426
|
+
|
|
427
|
+
window.YT.ready(function () {
|
|
428
|
+
if (isDestroyed || !document.getElementById(playerID)) return;
|
|
429
|
+
|
|
430
|
+
new window.YT.Player(playerID, {
|
|
431
|
+
height: "100%",
|
|
432
|
+
width: "100%",
|
|
433
|
+
videoId: videoID,
|
|
434
|
+
playerVars: {
|
|
435
|
+
autoplay: 1, // 在讀取時自動播放影片
|
|
436
|
+
controls: 1, // 在播放器顯示暫停/播放按鈕
|
|
437
|
+
disablekb: 1,
|
|
438
|
+
modestbranding: 1, // 隱藏 YouTube Logo
|
|
439
|
+
fs: 1, // 隱藏全螢幕按鈕
|
|
440
|
+
iv_load_policy: 3, // 隱藏影片註解
|
|
441
|
+
autohide: 1,
|
|
442
|
+
showinfo: 0,
|
|
443
|
+
rel: 0,
|
|
444
|
+
playsinline: 1,
|
|
445
|
+
enablejsapi: 1,
|
|
446
|
+
cc_lang_pref: "zh",
|
|
447
|
+
cc_load_policy: 0,
|
|
448
|
+
mute: 1,
|
|
449
|
+
},
|
|
450
|
+
events: {
|
|
451
|
+
onReady: onPlayerReady,
|
|
452
|
+
onStateChange: onPlayerStateChange,
|
|
453
|
+
},
|
|
454
|
+
});
|
|
455
|
+
});
|
|
456
|
+
};
|
|
457
|
+
const onPlayerReady = (event) => {
|
|
458
|
+
event.target.mute();
|
|
459
|
+
const state = event.target.playerInfo.playerState;
|
|
460
|
+
switch (state) {
|
|
461
|
+
case 0:
|
|
462
|
+
if (embla.autoPlayPause) return;
|
|
463
|
+
embla.canScrollNext() ? embla.scrollNext() : embla.scrollTo(0);
|
|
464
|
+
if (autoplayPlugin && !autoplayPlugin.isPlaying()) autoplayPlugin.play();
|
|
465
|
+
break;
|
|
466
|
+
case 1:
|
|
467
|
+
if (autoplayPlugin) autoplayPlugin.stop();
|
|
468
|
+
break;
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
const onPlayerStateChange = (event) => {
|
|
472
|
+
const state = event.data;
|
|
473
|
+
switch (state) {
|
|
474
|
+
case 0:
|
|
475
|
+
if (embla.autoPlayPause) return;
|
|
476
|
+
embla.canScrollNext() ? embla.scrollNext() : embla.scrollTo(0);
|
|
477
|
+
if (autoplayPlugin && !autoplayPlugin.isPlaying()) autoplayPlugin.play();
|
|
478
|
+
break;
|
|
479
|
+
case 1:
|
|
480
|
+
if (autoplayPlugin) autoplayPlugin.stop();
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
const getCurrentYtSlide = () => {
|
|
486
|
+
const activeSlide = slides[embla.selectedScrollSnap()];
|
|
487
|
+
return activeSlide?.querySelector("[data-type='youtube'][data-videoID]");
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
const createYtIframe = (ytVideo) => {
|
|
491
|
+
const videoID = ytVideo.getAttribute("data-videoID");
|
|
492
|
+
const playerWrap = document.createElement("div");
|
|
493
|
+
const playerID = `youtube-player__${ID}__${embla.selectedScrollSnap()}__${videoID}`;
|
|
494
|
+
|
|
495
|
+
playerWrap.id = playerID;
|
|
496
|
+
playerWrap.className = "youtube-player";
|
|
497
|
+
const oldPlayer = ytVideo.querySelector(".youtube-player");
|
|
498
|
+
if (oldPlayer) oldPlayer.remove();
|
|
499
|
+
ytVideo.appendChild(playerWrap);
|
|
500
|
+
requestAnimationFrame(() => onYouTubeIframeAPIReady(playerID, videoID));
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
const removeYtPlayers = () => {
|
|
504
|
+
slides.forEach((slide) => {
|
|
505
|
+
const players = slide.querySelectorAll(".youtube-player");
|
|
506
|
+
players.forEach((player) => player.remove());
|
|
507
|
+
});
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
const handleSelect = () => {
|
|
511
|
+
const ytVideo = getCurrentYtSlide();
|
|
512
|
+
removeYtPlayers();
|
|
513
|
+
|
|
514
|
+
if (ytVideo) {
|
|
515
|
+
stopAutoplayNextTick(autoplayPlugin);
|
|
516
|
+
if (isDestroyed || getCurrentYtSlide() !== ytVideo) return;
|
|
517
|
+
createYtIframe(ytVideo);
|
|
518
|
+
stopAutoplayNextTick(autoplayPlugin);
|
|
519
|
+
} else if (autoplayPlugin && !autoplayPlugin.isPlaying()) {
|
|
520
|
+
autoplayPlugin.play();
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
embla.on("select", handleSelect);
|
|
524
|
+
embla.on("destroy", () => {
|
|
525
|
+
isDestroyed = true;
|
|
526
|
+
removeYtPlayers();
|
|
527
|
+
});
|
|
528
|
+
handleSelect();
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// youku
|
|
532
|
+
export const setupYouku = (embla) => {
|
|
533
|
+
const autoplayPlugin = embla.plugins()?.autoplay;
|
|
534
|
+
const slides = embla.slideNodes();
|
|
535
|
+
|
|
536
|
+
let youkuTimer = null;
|
|
537
|
+
|
|
538
|
+
const clearYoukuTimer = () => {
|
|
539
|
+
if (!youkuTimer) return;
|
|
540
|
+
|
|
541
|
+
clearTimeout(youkuTimer);
|
|
542
|
+
youkuTimer = null;
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
const getCurrentYoukuSlide = () => {
|
|
546
|
+
const activeSlide = slides[embla.selectedScrollSnap()];
|
|
547
|
+
return activeSlide?.querySelector("[data-type='youku'][data-videoID]");
|
|
548
|
+
};
|
|
549
|
+
|
|
550
|
+
const getYoukuDuration = (youkuVideo) => {
|
|
551
|
+
const duration = Number(youkuVideo.getAttribute("data-duration"));
|
|
552
|
+
return Number.isFinite(duration) && duration > 0 ? duration * 1000 : 30000;
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
const createYoukuIframe = (youkuVideo) => {
|
|
556
|
+
const videoID = youkuVideo.getAttribute("data-videoID");
|
|
557
|
+
const playerWrap = document.createElement("div");
|
|
558
|
+
const youkuIframe = document.createElement("iframe");
|
|
559
|
+
|
|
560
|
+
playerWrap.className = "youku-player";
|
|
561
|
+
youkuIframe.src = `https://player.youku.com/embed/${videoID}?rel=0&autoplay=1&muted=1`;
|
|
562
|
+
const oldPlayer = youkuVideo.querySelector(".youku-player");
|
|
563
|
+
if (oldPlayer) oldPlayer.remove();
|
|
564
|
+
playerWrap.appendChild(youkuIframe);
|
|
565
|
+
youkuVideo.appendChild(playerWrap);
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const removeYoukuPlayers = () => {
|
|
569
|
+
slides.forEach((slide) => {
|
|
570
|
+
const players = slide.querySelectorAll(".youku-player iframe");
|
|
571
|
+
players.forEach((iframe) => {
|
|
572
|
+
iframe.src = "";
|
|
573
|
+
iframe.remove();
|
|
574
|
+
});
|
|
575
|
+
});
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
const handleSelect = () => {
|
|
579
|
+
const youkuVideo = getCurrentYoukuSlide();
|
|
580
|
+
clearYoukuTimer();
|
|
581
|
+
removeYoukuPlayers();
|
|
582
|
+
if (youkuVideo) {
|
|
583
|
+
createYoukuIframe(youkuVideo);
|
|
584
|
+
if (autoplayPlugin) {
|
|
585
|
+
stopAutoplayNextTick(autoplayPlugin);
|
|
586
|
+
youkuTimer = setTimeout(() => {
|
|
587
|
+
embla.scrollNext();
|
|
588
|
+
autoplayPlugin.play();
|
|
589
|
+
}, getYoukuDuration(youkuVideo));
|
|
590
|
+
}
|
|
591
|
+
} else if (autoplayPlugin && !autoplayPlugin.isPlaying()) {
|
|
592
|
+
autoplayPlugin.play();
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
embla.on("select", handleSelect);
|
|
597
|
+
embla.on("destroy", () => {
|
|
598
|
+
clearYoukuTimer();
|
|
599
|
+
removeYoukuPlayers();
|
|
600
|
+
});
|
|
601
|
+
|
|
602
|
+
handleSelect();
|
|
603
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export * as SwiperVer8 from "./swiperV8.plugin"
|
|
2
2
|
export * as SwiperVer11 from "./swiperV11.plugin"
|
|
3
|
+
export * as SwiperEmbla from "./embla.plugin";
|
|
3
4
|
export { default as gsap } from "./gsap.plugin"
|
|
4
5
|
export * from "./lazyLoad.plugin"
|
|
5
6
|
export * from "./tailwind.plugin"
|
|
@@ -24,3 +24,278 @@ export {
|
|
|
24
24
|
EffectCreative,
|
|
25
25
|
EffectCards,
|
|
26
26
|
} from "swiperv11/modules"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
const getSlides = (swiper) => Array.from(swiper?.slides || [])
|
|
30
|
+
|
|
31
|
+
const getActiveSlide = (swiper) => {
|
|
32
|
+
const slides = getSlides(swiper)
|
|
33
|
+
return slides[swiper?.activeIndex] || slides[swiper?.realIndex] || null
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const stopAutoplayNextTick = (swiper) => {
|
|
37
|
+
const autoplay = swiper?.autoplay
|
|
38
|
+
if (!autoplay) return
|
|
39
|
+
|
|
40
|
+
autoplay.stop()
|
|
41
|
+
setTimeout(() => {
|
|
42
|
+
autoplay.stop()
|
|
43
|
+
}, 100)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const startAutoplay = (swiper) => {
|
|
47
|
+
const autoplay = swiper?.autoplay
|
|
48
|
+
if (!autoplay || autoplay.running) return
|
|
49
|
+
|
|
50
|
+
autoplay.start()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const slideNextOrFirst = (swiper) => {
|
|
54
|
+
if (!swiper) return
|
|
55
|
+
|
|
56
|
+
if (!swiper.params?.loop && swiper.isEnd) {
|
|
57
|
+
swiper.slideTo(0)
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
swiper.slideNext()
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// video
|
|
65
|
+
export const setupVideo = (swiper) => {
|
|
66
|
+
const slides = getSlides(swiper)
|
|
67
|
+
if (!swiper || slides.length <= 1) return null
|
|
68
|
+
|
|
69
|
+
let timer = null
|
|
70
|
+
|
|
71
|
+
const clearVideoTimer = () => {
|
|
72
|
+
if (!timer) return
|
|
73
|
+
clearTimeout(timer)
|
|
74
|
+
timer = null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const pauseVideo = () => {
|
|
78
|
+
clearVideoTimer()
|
|
79
|
+
slides.forEach((slide) => {
|
|
80
|
+
const video = slide.querySelector("video")
|
|
81
|
+
if (!video) return
|
|
82
|
+
video.removeAttribute("loop")
|
|
83
|
+
video.pause()
|
|
84
|
+
video.currentTime = 0
|
|
85
|
+
video.onended = null
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const control = () => {
|
|
90
|
+
pauseVideo()
|
|
91
|
+
const currentSlide = getActiveSlide(swiper)
|
|
92
|
+
const video = currentSlide?.querySelector("video")
|
|
93
|
+
if (!video) {
|
|
94
|
+
startAutoplay(swiper)
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
timer = setTimeout(() => {
|
|
99
|
+
if (swiper.autoplay?.running) swiper.autoplay.stop()
|
|
100
|
+
}, 150)
|
|
101
|
+
|
|
102
|
+
video.onended = () => {
|
|
103
|
+
if (swiper.autoPlayPause) return
|
|
104
|
+
slideNextOrFirst(swiper)
|
|
105
|
+
startAutoplay(swiper)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
video.play()
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
control()
|
|
112
|
+
swiper.on("slideChange", control)
|
|
113
|
+
swiper.on("destroy", clearVideoTimer)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// yt
|
|
117
|
+
export const setupYt = (swiper, ID) => {
|
|
118
|
+
const slides = getSlides(swiper)
|
|
119
|
+
if (!swiper || slides.length <= 1) return null
|
|
120
|
+
|
|
121
|
+
let isDestroyed = false
|
|
122
|
+
let youtubePlayer = null
|
|
123
|
+
|
|
124
|
+
const getCurrentYtSlide = () => {
|
|
125
|
+
const activeSlide = getActiveSlide(swiper)
|
|
126
|
+
return activeSlide?.querySelector("[data-type='youtube'][data-videoID]")
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const destroyYoutubePlayer = () => {
|
|
130
|
+
if (youtubePlayer?.destroy) youtubePlayer.destroy()
|
|
131
|
+
youtubePlayer = null
|
|
132
|
+
|
|
133
|
+
slides.forEach((slide) => {
|
|
134
|
+
const players = slide.querySelectorAll(".youtube-player")
|
|
135
|
+
players.forEach((player) => player.remove())
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const handlePlayerStateChange = (event) => {
|
|
140
|
+
const state = event.data
|
|
141
|
+
|
|
142
|
+
switch (state) {
|
|
143
|
+
case 0:
|
|
144
|
+
if (swiper.autoPlayPause) return
|
|
145
|
+
|
|
146
|
+
slideNextOrFirst(swiper)
|
|
147
|
+
startAutoplay(swiper)
|
|
148
|
+
break
|
|
149
|
+
case 1:
|
|
150
|
+
stopAutoplayNextTick(swiper)
|
|
151
|
+
break
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const createYtIframe = (ytVideo) => {
|
|
156
|
+
if (!window.YT?.ready || !window.YT?.Player) return
|
|
157
|
+
|
|
158
|
+
const videoID = ytVideo.getAttribute("data-videoID")
|
|
159
|
+
const playerWrap = document.createElement("div")
|
|
160
|
+
const playerID = `youtube-player__${ID}__${swiper.activeIndex}__${videoID}`
|
|
161
|
+
|
|
162
|
+
playerWrap.id = playerID
|
|
163
|
+
playerWrap.className = "youtube-player"
|
|
164
|
+
|
|
165
|
+
destroyYoutubePlayer()
|
|
166
|
+
ytVideo.appendChild(playerWrap)
|
|
167
|
+
|
|
168
|
+
window.YT.ready(() => {
|
|
169
|
+
if (isDestroyed || !document.getElementById(playerID)) return
|
|
170
|
+
|
|
171
|
+
youtubePlayer = new window.YT.Player(playerID, {
|
|
172
|
+
height: "100%",
|
|
173
|
+
width: "100%",
|
|
174
|
+
videoId: videoID,
|
|
175
|
+
playerVars: {
|
|
176
|
+
autoplay: 1,
|
|
177
|
+
controls: 1,
|
|
178
|
+
disablekb: 1,
|
|
179
|
+
modestbranding: 1,
|
|
180
|
+
fs: 1,
|
|
181
|
+
iv_load_policy: 3,
|
|
182
|
+
autohide: 1,
|
|
183
|
+
showinfo: 0,
|
|
184
|
+
rel: 0,
|
|
185
|
+
playsinline: 1,
|
|
186
|
+
enablejsapi: 1,
|
|
187
|
+
cc_lang_pref: "zh",
|
|
188
|
+
cc_load_policy: 0,
|
|
189
|
+
mute: 1,
|
|
190
|
+
},
|
|
191
|
+
events: {
|
|
192
|
+
onReady: (event) => {
|
|
193
|
+
event.target.mute()
|
|
194
|
+
event.target.playVideo()
|
|
195
|
+
swiper.youtubePlayer = event.target
|
|
196
|
+
console.log(swiper.youtubePlayer);
|
|
197
|
+
},
|
|
198
|
+
onStateChange: handlePlayerStateChange,
|
|
199
|
+
},
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const handleSelect = () => {
|
|
205
|
+
const ytVideo = getCurrentYtSlide()
|
|
206
|
+
destroyYoutubePlayer()
|
|
207
|
+
|
|
208
|
+
if (ytVideo) {
|
|
209
|
+
stopAutoplayNextTick(swiper)
|
|
210
|
+
if (isDestroyed || getCurrentYtSlide() !== ytVideo) return
|
|
211
|
+
|
|
212
|
+
createYtIframe(ytVideo)
|
|
213
|
+
stopAutoplayNextTick(swiper)
|
|
214
|
+
} else {
|
|
215
|
+
startAutoplay(swiper)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
swiper.on("slideChange", handleSelect)
|
|
220
|
+
swiper.on("destroy", () => {
|
|
221
|
+
isDestroyed = true
|
|
222
|
+
destroyYoutubePlayer()
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
handleSelect()
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// youku
|
|
229
|
+
export const setupYouku = (swiper) => {
|
|
230
|
+
const slides = getSlides(swiper)
|
|
231
|
+
if (!swiper || slides.length <= 1) return null
|
|
232
|
+
|
|
233
|
+
let youkuTimer = null
|
|
234
|
+
|
|
235
|
+
const clearYoukuTimer = () => {
|
|
236
|
+
if (!youkuTimer) return
|
|
237
|
+
|
|
238
|
+
clearTimeout(youkuTimer)
|
|
239
|
+
youkuTimer = null
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const getCurrentYoukuSlide = () => {
|
|
243
|
+
const activeSlide = getActiveSlide(swiper)
|
|
244
|
+
return activeSlide?.querySelector("[data-type='youku'][data-videoID]")
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const createYoukuIframe = (youkuVideo) => {
|
|
248
|
+
const videoID = youkuVideo.getAttribute("data-videoID")
|
|
249
|
+
const playerWrap = document.createElement("div")
|
|
250
|
+
const youkuIframe = document.createElement("iframe")
|
|
251
|
+
playerWrap.className = "youku-player"
|
|
252
|
+
youkuIframe.src = `https://player.youku.com/embed/${videoID}?rel=0&autoplay=1&muted=1`
|
|
253
|
+
const oldPlayer = youkuVideo.querySelector(".youku-player")
|
|
254
|
+
if (oldPlayer) oldPlayer.remove()
|
|
255
|
+
playerWrap.appendChild(youkuIframe)
|
|
256
|
+
youkuVideo.appendChild(playerWrap)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const removeYoukuPlayers = () => {
|
|
260
|
+
slides.forEach((slide) => {
|
|
261
|
+
const players = slide.querySelectorAll(".youku-player")
|
|
262
|
+
players.forEach((player) => {
|
|
263
|
+
const iframe = player.querySelector("iframe")
|
|
264
|
+
if (iframe) iframe.src = ""
|
|
265
|
+
player.remove()
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const handleSelect = () => {
|
|
271
|
+
const youkuVideo = getCurrentYoukuSlide()
|
|
272
|
+
|
|
273
|
+
clearYoukuTimer()
|
|
274
|
+
removeYoukuPlayers()
|
|
275
|
+
|
|
276
|
+
if (youkuVideo) {
|
|
277
|
+
const number = Number(youkuVideo.getAttribute("data-duration"))
|
|
278
|
+
const duration = number > 0 ? number * 1000 : 30000
|
|
279
|
+
|
|
280
|
+
createYoukuIframe(youkuVideo)
|
|
281
|
+
stopAutoplayNextTick(swiper)
|
|
282
|
+
|
|
283
|
+
youkuTimer = setTimeout(() => {
|
|
284
|
+
if (swiper.autoPlayPause) return
|
|
285
|
+
|
|
286
|
+
slideNextOrFirst(swiper)
|
|
287
|
+
startAutoplay(swiper)
|
|
288
|
+
}, duration)
|
|
289
|
+
} else {
|
|
290
|
+
startAutoplay(swiper)
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
swiper.on("slideChange", handleSelect)
|
|
295
|
+
swiper.on("destroy", () => {
|
|
296
|
+
clearYoukuTimer()
|
|
297
|
+
removeYoukuPlayers()
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
handleSelect()
|
|
301
|
+
}
|
|
@@ -26,6 +26,8 @@ html(lang="zh-Hant-TW" data-overlayscrollbars-initialize)
|
|
|
26
26
|
block stylesheet
|
|
27
27
|
// 主頁面 CSS
|
|
28
28
|
link(rel="stylesheet", href=`/src/assets/css/pages/${css}.sass`)
|
|
29
|
+
// YT-API
|
|
30
|
+
script(src="https://www.youtube.com/iframe_api")
|
|
29
31
|
// 共用 JS
|
|
30
32
|
script(defer type="module" src=`/src/assets/js/apps/${page}/page.js`)
|
|
31
33
|
// 若 video4 有開 Ig reels, 引入以下網址
|
package/src/pages/index.pug
CHANGED
|
@@ -290,76 +290,64 @@ block content
|
|
|
290
290
|
li 藥品服務
|
|
291
291
|
dropdown-el.dropdown4.disabled(d4-placeholder="請先選擇服務分類")
|
|
292
292
|
section.section6
|
|
293
|
+
-
|
|
294
|
+
const videoDemoData = [
|
|
295
|
+
{
|
|
296
|
+
type: 'youtube',
|
|
297
|
+
title: 'YouTube Demo',
|
|
298
|
+
videoID: 'zKYNITx17Mw',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
type: 'youku',
|
|
302
|
+
title: 'Youku Demo',
|
|
303
|
+
videoID: 'XNjQ5NDk1NTc4OA',
|
|
304
|
+
duration: 3,
|
|
305
|
+
},
|
|
306
|
+
{
|
|
307
|
+
type: 'video',
|
|
308
|
+
title: 'video Demo',
|
|
309
|
+
videoID: 'https://cdn.wdd.idv.tw/video/chappie_Video1.mp4',
|
|
310
|
+
},
|
|
311
|
+
]
|
|
293
312
|
.container
|
|
294
|
-
h3
|
|
313
|
+
h3 VIDEO 👇
|
|
295
314
|
.wrap
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
p bbbbb
|
|
315
|
-
p bbbbb
|
|
316
|
-
p bbbbb
|
|
317
|
-
p bbbbb
|
|
318
|
-
.collapseItem(partnerCollapse-wrapper)
|
|
319
|
-
.collapseTitle(collapse-block)
|
|
320
|
-
p CCCCC
|
|
321
|
-
i.arrow
|
|
322
|
-
.collapseContent(collapse-target)
|
|
323
|
-
.innerBox
|
|
324
|
-
.content
|
|
325
|
-
p ccccc
|
|
326
|
-
p ccccc
|
|
327
|
-
p ccccc
|
|
328
|
-
p ccccc
|
|
315
|
+
//
|
|
316
|
+
.titleBox
|
|
317
|
+
.heading
|
|
318
|
+
.sub-title EMBLA
|
|
319
|
+
.embla.embla-video-demo
|
|
320
|
+
.embla__viewport
|
|
321
|
+
.embla__container
|
|
322
|
+
each item in videoDemoData
|
|
323
|
+
.embla__slide
|
|
324
|
+
.video-demo-card
|
|
325
|
+
.video-demo-label
|
|
326
|
+
span= item.title
|
|
327
|
+
small= item.videoID
|
|
328
|
+
if item.type === 'video'
|
|
329
|
+
.mediaBox
|
|
330
|
+
video(src=item.videoID muted playsinline)
|
|
331
|
+
else
|
|
332
|
+
.mediaBox(data-type=item.type data-videoID=item.videoID data-duration=item.duration?item.duration:false)
|
|
329
333
|
.wrap
|
|
330
|
-
.
|
|
331
|
-
.
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
.
|
|
335
|
-
|
|
336
|
-
.
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
.content
|
|
348
|
-
p bbbbb
|
|
349
|
-
p bbbbb
|
|
350
|
-
p bbbbb
|
|
351
|
-
p bbbbb
|
|
352
|
-
.collapseItem(data-collapse)
|
|
353
|
-
.collapseTitle(data-collapse-click)
|
|
354
|
-
p CCCCC
|
|
355
|
-
i.arrow
|
|
356
|
-
.collapseBox(data-collapse-content)
|
|
357
|
-
.innerBox
|
|
358
|
-
.content
|
|
359
|
-
p ccccc
|
|
360
|
-
p ccccc
|
|
361
|
-
p ccccc
|
|
362
|
-
p ccccc
|
|
334
|
+
.titleBox
|
|
335
|
+
.heading
|
|
336
|
+
.sub-title SWIPER
|
|
337
|
+
.swiper.swiper-video-demo
|
|
338
|
+
.swiper-wrapper
|
|
339
|
+
each item in videoDemoData
|
|
340
|
+
.swiper-slide
|
|
341
|
+
.video-demo-card
|
|
342
|
+
.video-demo-label
|
|
343
|
+
span= item.title
|
|
344
|
+
small= item.videoID
|
|
345
|
+
if item.type === 'video'
|
|
346
|
+
.mediaBox
|
|
347
|
+
video(src=item.videoID muted playsinline)
|
|
348
|
+
else
|
|
349
|
+
.mediaBox(data-type=item.type data-videoID=item.videoID data-duration=item.duration?item.duration:false)
|
|
350
|
+
|
|
363
351
|
section.section7
|
|
364
352
|
.container
|
|
365
353
|
h3 Ripple4 👇
|