peek-carousel 1.0.2
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/LICENSE +21 -0
- package/README.en.md +238 -0
- package/README.md +234 -0
- package/dist/peek-carousel.css +1 -0
- package/dist/peek-carousel.esm.js +1368 -0
- package/dist/peek-carousel.esm.js.map +1 -0
- package/dist/peek-carousel.esm.min.js +8 -0
- package/dist/peek-carousel.esm.min.js.map +1 -0
- package/dist/peek-carousel.js +1376 -0
- package/dist/peek-carousel.js.map +1 -0
- package/dist/peek-carousel.min.css +1 -0
- package/dist/peek-carousel.min.js +8 -0
- package/dist/peek-carousel.min.js.map +1 -0
- package/examples/example-built.html +367 -0
- package/examples/example.css +2216 -0
- package/examples/example.js +404 -0
- package/examples/example.min.css +1 -0
- package/examples/example.min.js +1 -0
- package/package.json +92 -0
- package/src/core/PeekCarousel.js +294 -0
- package/src/core/config.js +49 -0
- package/src/core/constants.js +73 -0
- package/src/modules/Animator.js +244 -0
- package/src/modules/AutoRotate.js +43 -0
- package/src/modules/EventHandler.js +390 -0
- package/src/modules/Navigator.js +116 -0
- package/src/modules/UIManager.js +170 -0
- package/src/peek-carousel.d.ts +34 -0
- package/src/styles/base/_accessibility.scss +16 -0
- package/src/styles/base/_mixins.scss +7 -0
- package/src/styles/base/_variables.scss +75 -0
- package/src/styles/components/_carousel.scss +5 -0
- package/src/styles/components/_counter.scss +109 -0
- package/src/styles/components/_indicators.scss +154 -0
- package/src/styles/components/_navigation.scss +193 -0
- package/src/styles/components/carousel/_carousel-base.scss +99 -0
- package/src/styles/components/carousel/_carousel-classic.scss +76 -0
- package/src/styles/components/carousel/_carousel-mixins.scss +18 -0
- package/src/styles/components/carousel/_carousel-radial.scss +72 -0
- package/src/styles/components/carousel/_carousel-stack.scss +84 -0
- package/src/styles/components/carousel/_carousel-variables.scss +118 -0
- package/src/styles/peek-carousel.scss +11 -0
- package/src/utils/dom.js +53 -0
- package/src/utils/helpers.js +46 -0
- package/src/utils/icons.js +92 -0
- package/src/utils/preloader.js +69 -0
- package/types/index.d.ts +34 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
@use '../../base/variables' as *;
|
|
2
|
+
@use 'carousel-variables' as *;
|
|
3
|
+
@use 'carousel-mixins' as *;
|
|
4
|
+
@use 'sass:list';
|
|
5
|
+
|
|
6
|
+
.peek-carousel--classic {
|
|
7
|
+
overflow: hidden;
|
|
8
|
+
|
|
9
|
+
.peek-carousel__track {
|
|
10
|
+
transition: transform $transition-smooth;
|
|
11
|
+
transform: translateX(calc(var(--track-offset, 0px) + var(--drag-offset)));
|
|
12
|
+
|
|
13
|
+
&:active {
|
|
14
|
+
transition: transform $drag-transition-duration;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.peek-carousel__item {
|
|
19
|
+
position: absolute;
|
|
20
|
+
left: var(--item-x, 50%);
|
|
21
|
+
transform: translate(-50%, -50%) scale(var(--item-scale, 1));
|
|
22
|
+
opacity: 0;
|
|
23
|
+
visibility: hidden;
|
|
24
|
+
cursor: default;
|
|
25
|
+
z-index: $z-index-base;
|
|
26
|
+
@include classic-item-transition;
|
|
27
|
+
|
|
28
|
+
&--active {
|
|
29
|
+
opacity: 1;
|
|
30
|
+
visibility: visible;
|
|
31
|
+
z-index: $z-index-active;
|
|
32
|
+
cursor: grab;
|
|
33
|
+
@include classic-item-transition(0s);
|
|
34
|
+
|
|
35
|
+
&:active {
|
|
36
|
+
cursor: grabbing;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.peek-carousel__image {
|
|
40
|
+
box-shadow: list.join(
|
|
41
|
+
(0 40px 100px $color-primary-60,
|
|
42
|
+
0 0 40px $color-primary-40,
|
|
43
|
+
0 0 80px $color-secondary),
|
|
44
|
+
(),
|
|
45
|
+
$separator: comma
|
|
46
|
+
);
|
|
47
|
+
filter: brightness($classic-active-brightness) saturate($classic-active-saturation);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
&--prev,
|
|
52
|
+
&--next {
|
|
53
|
+
opacity: $classic-peek-opacity;
|
|
54
|
+
visibility: visible;
|
|
55
|
+
z-index: $z-index-item;
|
|
56
|
+
cursor: pointer;
|
|
57
|
+
@include classic-item-transition(0s);
|
|
58
|
+
|
|
59
|
+
.peek-carousel__image {
|
|
60
|
+
filter: brightness($classic-peek-brightness) saturate($classic-peek-saturation);
|
|
61
|
+
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.5);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
&:hover .peek-carousel__image {
|
|
65
|
+
filter: brightness($classic-peek-hover-brightness) saturate($classic-peek-hover-saturation) !important;
|
|
66
|
+
box-shadow: 0 30px 70px $color-primary-30 !important;
|
|
67
|
+
transition: all $transition-base;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&:not(&--active):not(&--prev):not(&--next) .peek-carousel__image {
|
|
72
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
|
73
|
+
filter: brightness($classic-hidden-brightness) saturate($classic-hidden-saturation);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@use '../../base/variables' as *;
|
|
2
|
+
|
|
3
|
+
@mixin base-item-transition($visibility-delay: 0s) {
|
|
4
|
+
transition:
|
|
5
|
+
transform $transition-smooth,
|
|
6
|
+
opacity $transition-smooth,
|
|
7
|
+
visibility 0s $visibility-delay,
|
|
8
|
+
z-index 0s;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@mixin classic-item-transition($visibility-delay: $transition-smooth) {
|
|
12
|
+
transition:
|
|
13
|
+
transform $transition-smooth,
|
|
14
|
+
opacity $transition-smooth,
|
|
15
|
+
left $transition-smooth,
|
|
16
|
+
visibility 0s $visibility-delay,
|
|
17
|
+
z-index 0s;
|
|
18
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
@use '../../base/variables' as *;
|
|
2
|
+
@use 'carousel-variables' as *;
|
|
3
|
+
@use 'carousel-mixins' as *;
|
|
4
|
+
@use 'sass:list';
|
|
5
|
+
|
|
6
|
+
.peek-carousel--radial {
|
|
7
|
+
perspective: $perspective-distance;
|
|
8
|
+
|
|
9
|
+
.peek-carousel__track {
|
|
10
|
+
transition: transform $transition-smooth;
|
|
11
|
+
transform: rotateY(calc(var(--carousel-rotation, 0deg) + var(--drag-rotation-y)));
|
|
12
|
+
|
|
13
|
+
&:active {
|
|
14
|
+
transition: transform $drag-transition-duration;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.peek-carousel__item {
|
|
19
|
+
opacity: 0;
|
|
20
|
+
visibility: hidden;
|
|
21
|
+
cursor: default;
|
|
22
|
+
transform: translate(-50%, -50%)
|
|
23
|
+
rotateY(var(--item-angle))
|
|
24
|
+
translateZ(var(--item-radius));
|
|
25
|
+
@include base-item-transition($radial-visibility-delay);
|
|
26
|
+
|
|
27
|
+
&--active {
|
|
28
|
+
opacity: 1;
|
|
29
|
+
visibility: visible;
|
|
30
|
+
z-index: $z-index-active;
|
|
31
|
+
cursor: grab;
|
|
32
|
+
@include base-item-transition(0s);
|
|
33
|
+
|
|
34
|
+
&:active {
|
|
35
|
+
cursor: grabbing;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.peek-carousel__image {
|
|
39
|
+
box-shadow: list.join(
|
|
40
|
+
(0 40px 100px $color-primary-60,
|
|
41
|
+
0 0 40px $color-primary-40,
|
|
42
|
+
0 0 80px $color-secondary),
|
|
43
|
+
(),
|
|
44
|
+
$separator: comma
|
|
45
|
+
);
|
|
46
|
+
filter: brightness($radial-active-brightness) saturate($radial-active-saturation);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
&--prev,
|
|
51
|
+
&--next {
|
|
52
|
+
opacity: $radial-peek-opacity;
|
|
53
|
+
visibility: visible;
|
|
54
|
+
z-index: $z-index-item;
|
|
55
|
+
@include base-item-transition(0s);
|
|
56
|
+
|
|
57
|
+
.peek-carousel__image {
|
|
58
|
+
filter: brightness($radial-peek-brightness) saturate($radial-peek-saturation);
|
|
59
|
+
box-shadow: 0 25px 60px rgba(0, 0, 0, 0.6);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&:not(&--active) .peek-carousel__image {
|
|
64
|
+
filter: brightness($radial-inactive-brightness) saturate($radial-inactive-saturation);
|
|
65
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (max-width: $breakpoint-mobile) {
|
|
69
|
+
--item-radius: #{$item-mobile-radius};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
@use '../../base/variables' as *;
|
|
2
|
+
@use 'carousel-variables' as *;
|
|
3
|
+
@use 'sass:list';
|
|
4
|
+
|
|
5
|
+
.peek-carousel--stack {
|
|
6
|
+
.peek-carousel__track {
|
|
7
|
+
transition: none;
|
|
8
|
+
transform: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.peek-carousel__item {
|
|
12
|
+
cursor: pointer;
|
|
13
|
+
opacity: $stack-inactive-opacity;
|
|
14
|
+
visibility: visible;
|
|
15
|
+
transition:
|
|
16
|
+
transform $drag-transition-duration,
|
|
17
|
+
opacity $drag-transition-duration,
|
|
18
|
+
z-index 0s;
|
|
19
|
+
|
|
20
|
+
&:not(.peek-carousel__item--dragging-left):not(.peek-carousel__item--dragging-right) {
|
|
21
|
+
transition:
|
|
22
|
+
transform $stack-normal-transition,
|
|
23
|
+
opacity $stack-normal-transition,
|
|
24
|
+
z-index 0s;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
&--active {
|
|
28
|
+
opacity: 1;
|
|
29
|
+
z-index: $z-index-active;
|
|
30
|
+
|
|
31
|
+
.peek-carousel__image {
|
|
32
|
+
box-shadow: list.join(
|
|
33
|
+
(0 40px 100px $color-primary-60,
|
|
34
|
+
0 0 40px $color-primary-40,
|
|
35
|
+
0 0 80px $color-secondary),
|
|
36
|
+
(),
|
|
37
|
+
$separator: comma
|
|
38
|
+
);
|
|
39
|
+
filter: brightness($stack-active-brightness) saturate($stack-active-saturation);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
&:not(&--active) {
|
|
44
|
+
.peek-carousel__image {
|
|
45
|
+
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
|
|
46
|
+
filter: brightness($stack-inactive-brightness) saturate($stack-inactive-saturation);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
&:hover .peek-carousel__image {
|
|
50
|
+
filter: brightness($stack-hover-brightness) saturate($stack-hover-saturation) !important;
|
|
51
|
+
box-shadow: 0 30px 70px $color-primary-30 !important;
|
|
52
|
+
transition: all $transition-base;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
&--center {
|
|
57
|
+
transform: translate(-50%, -50%)
|
|
58
|
+
scale(1.15)
|
|
59
|
+
translateX(var(--drag-offset))
|
|
60
|
+
rotateZ(var(--drag-rotation));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
&--prev {
|
|
64
|
+
transform: translate(calc(-50% - 45%), -50%)
|
|
65
|
+
scale(0.85)
|
|
66
|
+
translateX(calc(-15px + var(--drag-offset)))
|
|
67
|
+
rotateZ(var(--drag-rotation));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
&--next {
|
|
71
|
+
transform: translate(calc(-50% + 45%), -50%)
|
|
72
|
+
scale(0.85)
|
|
73
|
+
translateX(calc(15px + var(--drag-offset)))
|
|
74
|
+
rotateZ(var(--drag-rotation));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
&--hidden {
|
|
78
|
+
transform: translate(-50%, -50%)
|
|
79
|
+
scale(0.7)
|
|
80
|
+
translateX(var(--drag-offset))
|
|
81
|
+
rotateZ(var(--drag-rotation));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
@use '../../base/variables' as *;
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
|
|
4
|
+
$breakpoint-mobile: 768px;
|
|
5
|
+
$drag-transition-duration: 0.05s;
|
|
6
|
+
$perspective-distance: 1200px;
|
|
7
|
+
|
|
8
|
+
$carousel-animation: (
|
|
9
|
+
duration: 0.8s,
|
|
10
|
+
delay: 0.1s,
|
|
11
|
+
initial-offset: 30px
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
// 아이템 크기 설정
|
|
15
|
+
$item-config: (
|
|
16
|
+
default: (
|
|
17
|
+
scale: 0.8,
|
|
18
|
+
radius: 400px
|
|
19
|
+
),
|
|
20
|
+
mobile: (
|
|
21
|
+
min-width: 280px,
|
|
22
|
+
max-width: 400px,
|
|
23
|
+
radius: 300px
|
|
24
|
+
)
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
// 하위 호환성 - 개별 변수
|
|
28
|
+
$animation-duration: map.get($carousel-animation, duration);
|
|
29
|
+
$animation-delay: map.get($carousel-animation, delay);
|
|
30
|
+
$initial-offset: map.get($carousel-animation, initial-offset);
|
|
31
|
+
|
|
32
|
+
$item-default-scale: map.get(map.get($item-config, default), scale);
|
|
33
|
+
$item-default-radius: map.get(map.get($item-config, default), radius);
|
|
34
|
+
$item-mobile-min-width: map.get(map.get($item-config, mobile), min-width);
|
|
35
|
+
$item-mobile-max-width: map.get(map.get($item-config, mobile), max-width);
|
|
36
|
+
$item-mobile-radius: map.get(map.get($item-config, mobile), radius);
|
|
37
|
+
|
|
38
|
+
$stack-mode: (
|
|
39
|
+
inactive-opacity: 0.5,
|
|
40
|
+
normal-transition: 0.5s,
|
|
41
|
+
active: (
|
|
42
|
+
brightness: 1.2,
|
|
43
|
+
saturation: 1.1
|
|
44
|
+
),
|
|
45
|
+
inactive: (
|
|
46
|
+
brightness: 0.7,
|
|
47
|
+
saturation: 0.85
|
|
48
|
+
),
|
|
49
|
+
hover: (
|
|
50
|
+
brightness: 0.9,
|
|
51
|
+
saturation: 1
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
$stack-inactive-opacity: map.get($stack-mode, inactive-opacity);
|
|
56
|
+
$stack-normal-transition: map.get($stack-mode, normal-transition);
|
|
57
|
+
$stack-active-brightness: map.get(map.get($stack-mode, active), brightness);
|
|
58
|
+
$stack-active-saturation: map.get(map.get($stack-mode, active), saturation);
|
|
59
|
+
$stack-inactive-brightness: map.get(map.get($stack-mode, inactive), brightness);
|
|
60
|
+
$stack-inactive-saturation: map.get(map.get($stack-mode, inactive), saturation);
|
|
61
|
+
$stack-hover-brightness: map.get(map.get($stack-mode, hover), brightness);
|
|
62
|
+
$stack-hover-saturation: map.get(map.get($stack-mode, hover), saturation);
|
|
63
|
+
|
|
64
|
+
$radial-mode: (
|
|
65
|
+
visibility-delay: 0.5s,
|
|
66
|
+
peek-opacity: 0.3,
|
|
67
|
+
active: (
|
|
68
|
+
brightness: 1.2,
|
|
69
|
+
saturation: 1.1
|
|
70
|
+
),
|
|
71
|
+
inactive: (
|
|
72
|
+
brightness: 0.7,
|
|
73
|
+
saturation: 0.85
|
|
74
|
+
),
|
|
75
|
+
peek: (
|
|
76
|
+
brightness: 0.8,
|
|
77
|
+
saturation: 0.9
|
|
78
|
+
)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
$radial-visibility-delay: map.get($radial-mode, visibility-delay);
|
|
82
|
+
$radial-peek-opacity: map.get($radial-mode, peek-opacity);
|
|
83
|
+
$radial-active-brightness: map.get(map.get($radial-mode, active), brightness);
|
|
84
|
+
$radial-active-saturation: map.get(map.get($radial-mode, active), saturation);
|
|
85
|
+
$radial-inactive-brightness: map.get(map.get($radial-mode, inactive), brightness);
|
|
86
|
+
$radial-inactive-saturation: map.get(map.get($radial-mode, inactive), saturation);
|
|
87
|
+
$radial-peek-brightness: map.get(map.get($radial-mode, peek), brightness);
|
|
88
|
+
$radial-peek-saturation: map.get(map.get($radial-mode, peek), saturation);
|
|
89
|
+
|
|
90
|
+
$classic-mode: (
|
|
91
|
+
peek-opacity: 0.6,
|
|
92
|
+
active: (
|
|
93
|
+
brightness: 1.15,
|
|
94
|
+
saturation: 1.1
|
|
95
|
+
),
|
|
96
|
+
peek: (
|
|
97
|
+
brightness: 0.8,
|
|
98
|
+
saturation: 0.95
|
|
99
|
+
),
|
|
100
|
+
peek-hover: (
|
|
101
|
+
brightness: 1,
|
|
102
|
+
saturation: 1.05
|
|
103
|
+
),
|
|
104
|
+
hidden: (
|
|
105
|
+
brightness: 0.5,
|
|
106
|
+
saturation: 0.7
|
|
107
|
+
)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
$classic-peek-opacity: map.get($classic-mode, peek-opacity);
|
|
111
|
+
$classic-active-brightness: map.get(map.get($classic-mode, active), brightness);
|
|
112
|
+
$classic-active-saturation: map.get(map.get($classic-mode, active), saturation);
|
|
113
|
+
$classic-peek-brightness: map.get(map.get($classic-mode, peek), brightness);
|
|
114
|
+
$classic-peek-saturation: map.get(map.get($classic-mode, peek), saturation);
|
|
115
|
+
$classic-peek-hover-brightness: map.get(map.get($classic-mode, peek-hover), brightness);
|
|
116
|
+
$classic-peek-hover-saturation: map.get(map.get($classic-mode, peek-hover), saturation);
|
|
117
|
+
$classic-hidden-brightness: map.get(map.get($classic-mode, hidden), brightness);
|
|
118
|
+
$classic-hidden-saturation: map.get(map.get($classic-mode, hidden), saturation);
|
package/src/utils/dom.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export function getElement(selector) {
|
|
2
|
+
if (typeof selector === 'string') {
|
|
3
|
+
return document.querySelector(selector);
|
|
4
|
+
}
|
|
5
|
+
return selector instanceof HTMLElement ? selector : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function getElements(selector, parent = document) {
|
|
9
|
+
if (typeof selector !== 'string') return [];
|
|
10
|
+
return Array.from(parent.querySelectorAll(selector));
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function addClass(element, ...classes) {
|
|
14
|
+
if (element instanceof HTMLElement && classes.length > 0) {
|
|
15
|
+
element.classList.add(...classes);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function removeClass(element, ...classes) {
|
|
20
|
+
if (element instanceof HTMLElement && classes.length > 0) {
|
|
21
|
+
element.classList.remove(...classes);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function toggleClass(element, className, force) {
|
|
26
|
+
if (element instanceof HTMLElement && className) {
|
|
27
|
+
element.classList.toggle(className, force);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function setStyle(element, property, value) {
|
|
32
|
+
if (element instanceof HTMLElement && property) {
|
|
33
|
+
element.style[property] = value;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function setCSSVar(element, property, value) {
|
|
38
|
+
if (element instanceof HTMLElement && property) {
|
|
39
|
+
element.style.setProperty(property, value);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function setAttribute(element, name, value) {
|
|
44
|
+
if (element instanceof HTMLElement && name) {
|
|
45
|
+
element.setAttribute(name, value);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function removeAttribute(element, name) {
|
|
50
|
+
if (element instanceof HTMLElement && name) {
|
|
51
|
+
element.removeAttribute(name);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function isMobile() {
|
|
2
|
+
return window.innerWidth <= 768;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function normalizeIndex(index, length) {
|
|
6
|
+
if (length <= 0) return 0;
|
|
7
|
+
return ((index % length) + length) % length;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function clamp(value, min, max) {
|
|
11
|
+
if (min > max) [min, max] = [max, min];
|
|
12
|
+
return Math.min(Math.max(value, min), max);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function debounce(func, wait) {
|
|
16
|
+
if (typeof func !== 'function') {
|
|
17
|
+
throw new TypeError('첫 번째 인자는 함수여야 합니다');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
let timeout;
|
|
21
|
+
return function executedFunction(...args) {
|
|
22
|
+
const context = this;
|
|
23
|
+
const later = () => {
|
|
24
|
+
clearTimeout(timeout);
|
|
25
|
+
func.apply(context, args);
|
|
26
|
+
};
|
|
27
|
+
clearTimeout(timeout);
|
|
28
|
+
timeout = setTimeout(later, wait);
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function throttle(func, limit) {
|
|
33
|
+
if (typeof func !== 'function') {
|
|
34
|
+
throw new TypeError('첫 번째 인자는 함수여야 합니다');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let inThrottle;
|
|
38
|
+
return function executedFunction(...args) {
|
|
39
|
+
const context = this;
|
|
40
|
+
if (!inThrottle) {
|
|
41
|
+
func.apply(context, args);
|
|
42
|
+
inThrottle = true;
|
|
43
|
+
setTimeout(() => (inThrottle = false), limit);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const iconCache = new Map();
|
|
2
|
+
|
|
3
|
+
function createSVGIcon(path, options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
width = 24,
|
|
6
|
+
height = 24,
|
|
7
|
+
viewBox = '0 0 24 24',
|
|
8
|
+
fill = 'none',
|
|
9
|
+
stroke = 'currentColor',
|
|
10
|
+
strokeWidth = 2,
|
|
11
|
+
strokeLinecap = 'round',
|
|
12
|
+
strokeLinejoin = 'round',
|
|
13
|
+
className = '',
|
|
14
|
+
} = options;
|
|
15
|
+
|
|
16
|
+
const cacheKey = `${path}-${JSON.stringify(options)}`;
|
|
17
|
+
if (iconCache.has(cacheKey)) {
|
|
18
|
+
return iconCache.get(cacheKey);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const svg = `<svg width="${width}" height="${height}" viewBox="${viewBox}" fill="${fill}" xmlns="http://www.w3.org/2000/svg" class="${className}"><path d="${path}" stroke="${stroke}" stroke-width="${strokeWidth}" stroke-linecap="${strokeLinecap}" stroke-linejoin="${strokeLinejoin}"/></svg>`;
|
|
22
|
+
|
|
23
|
+
iconCache.set(cacheKey, svg);
|
|
24
|
+
return svg;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const ICONS = {
|
|
28
|
+
prev: {
|
|
29
|
+
path: 'M15 18L9 12L15 6',
|
|
30
|
+
options: {},
|
|
31
|
+
},
|
|
32
|
+
next: {
|
|
33
|
+
path: 'M9 18L15 12L9 6',
|
|
34
|
+
options: {},
|
|
35
|
+
},
|
|
36
|
+
play: {
|
|
37
|
+
path: 'M8 6.5v11l9-5.5z',
|
|
38
|
+
options: {
|
|
39
|
+
fill: 'currentColor',
|
|
40
|
+
stroke: 'currentColor',
|
|
41
|
+
strokeWidth: 0,
|
|
42
|
+
strokeLinecap: 'round',
|
|
43
|
+
strokeLinejoin: 'round',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
pause: {
|
|
47
|
+
path: 'M7 5.5C7 5.22386 7.22386 5 7.5 5H9.5C9.77614 5 10 5.22386 10 5.5V18.5C10 18.7761 9.77614 19 9.5 19H7.5C7.22386 19 7 18.7761 7 18.5V5.5ZM14 5.5C14 5.22386 14.2239 5 14.5 5H16.5C16.7761 5 17 5.22386 17 5.5V18.5C17 18.7761 16.7761 19 16.5 19H14.5C14.2239 19 14 18.7761 14 18.5V5.5Z',
|
|
48
|
+
options: {
|
|
49
|
+
fill: 'currentColor',
|
|
50
|
+
stroke: 'none',
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export function getIcon(iconName, customOptions = {}) {
|
|
56
|
+
const icon = ICONS[iconName];
|
|
57
|
+
if (!icon) {
|
|
58
|
+
console.warn(`PeekCarousel: 아이콘 "${iconName}"을 찾을 수 없습니다`);
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const options = { ...icon.options, ...customOptions };
|
|
63
|
+
return createSVGIcon(icon.path, options);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function injectIcon(button, iconName, options = {}) {
|
|
67
|
+
if (!button || !(button instanceof HTMLElement)) return;
|
|
68
|
+
|
|
69
|
+
if (button.querySelector('svg')) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const iconHTML = getIcon(iconName, options);
|
|
74
|
+
if (iconHTML) {
|
|
75
|
+
button.innerHTML = iconHTML;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function injectAutoRotateIcons(button) {
|
|
80
|
+
if (!button || !(button instanceof HTMLElement)) return;
|
|
81
|
+
|
|
82
|
+
if (button.querySelector('svg')) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const playHTML = getIcon('play', { className: 'play-icon' });
|
|
87
|
+
const pauseHTML = getIcon('pause', { className: 'pause-icon' });
|
|
88
|
+
|
|
89
|
+
if (playHTML && pauseHTML) {
|
|
90
|
+
button.innerHTML = playHTML + pauseHTML;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const loadingCache = new Map();
|
|
2
|
+
|
|
3
|
+
export function preloadImage(src) {
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
if (!src) {
|
|
6
|
+
reject(new Error('이미지 소스가 제공되지 않았습니다'));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (loadingCache.has(src)) {
|
|
11
|
+
return loadingCache.get(src);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const img = new Image();
|
|
15
|
+
const promise = new Promise((res, rej) => {
|
|
16
|
+
img.onload = () => {
|
|
17
|
+
loadingCache.delete(src);
|
|
18
|
+
res(img);
|
|
19
|
+
};
|
|
20
|
+
img.onerror = () => {
|
|
21
|
+
loadingCache.delete(src);
|
|
22
|
+
rej(new Error(`이미지 로드 실패: ${src}`));
|
|
23
|
+
};
|
|
24
|
+
img.src = src;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
loadingCache.set(src, promise);
|
|
28
|
+
promise.then(resolve, reject);
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function preloadImages(sources) {
|
|
33
|
+
if (!sources || sources.length === 0) {
|
|
34
|
+
return Promise.resolve([]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const uniqueSources = [...new Set(sources)];
|
|
38
|
+
return Promise.all(uniqueSources.map(src => preloadImage(src)));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function preloadImagesInRange(items, currentIndex, range) {
|
|
42
|
+
if (!items || items.length === 0 || range < 0) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const totalItems = items.length;
|
|
47
|
+
const imagesToPreload = new Set();
|
|
48
|
+
|
|
49
|
+
for (let distance = 1; distance <= range; distance++) {
|
|
50
|
+
const prevIndex = (currentIndex - distance + totalItems) % totalItems;
|
|
51
|
+
const nextIndex = (currentIndex + distance) % totalItems;
|
|
52
|
+
|
|
53
|
+
const prevImg = items[prevIndex]?.querySelector('img');
|
|
54
|
+
const nextImg = items[nextIndex]?.querySelector('img');
|
|
55
|
+
|
|
56
|
+
if (prevImg && prevImg.src && !prevImg.complete) {
|
|
57
|
+
imagesToPreload.add(prevImg.src);
|
|
58
|
+
}
|
|
59
|
+
if (nextImg && nextImg.src && !nextImg.complete) {
|
|
60
|
+
imagesToPreload.add(nextImg.src);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (imagesToPreload.size > 0) {
|
|
65
|
+
preloadImages([...imagesToPreload]).catch(err => {
|
|
66
|
+
console.warn('일부 이미지 프리로드 실패:', err);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface PeekCarouselOptions {
|
|
2
|
+
startIndex?: number;
|
|
3
|
+
layoutMode?: 'stack' | 'radial' | 'classic';
|
|
4
|
+
autoRotate?: boolean;
|
|
5
|
+
autoRotateInterval?: number;
|
|
6
|
+
swipeThreshold?: number;
|
|
7
|
+
dragThreshold?: number;
|
|
8
|
+
preloadRange?: number;
|
|
9
|
+
enableKeyboard?: boolean;
|
|
10
|
+
enableWheel?: boolean;
|
|
11
|
+
enableTouch?: boolean;
|
|
12
|
+
enableMouse?: boolean;
|
|
13
|
+
showNavigation?: boolean;
|
|
14
|
+
showCounter?: boolean;
|
|
15
|
+
showIndicators?: boolean;
|
|
16
|
+
showAutoRotateButton?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default class PeekCarousel {
|
|
20
|
+
constructor(selector: string | HTMLElement, options?: PeekCarouselOptions);
|
|
21
|
+
|
|
22
|
+
get currentIndex(): number;
|
|
23
|
+
get totalItems(): number;
|
|
24
|
+
get isAutoRotating(): boolean;
|
|
25
|
+
|
|
26
|
+
next(): void;
|
|
27
|
+
prev(): void;
|
|
28
|
+
goTo(index: number): void;
|
|
29
|
+
startAutoRotate(): void;
|
|
30
|
+
stopAutoRotate(): void;
|
|
31
|
+
toggleAutoRotate(): void;
|
|
32
|
+
updateLayoutClass(): void;
|
|
33
|
+
destroy(): void;
|
|
34
|
+
}
|