srcdev-nuxt-components 6.1.42 → 6.2.1
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.
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<component
|
|
3
|
+
:is="tag"
|
|
4
|
+
class="wipe-away-vertical"
|
|
5
|
+
:class="[elementClasses]"
|
|
6
|
+
:style="{ 'timeline-scope': timelineScope }"
|
|
7
|
+
ref="scrollContainerRef"
|
|
8
|
+
>
|
|
9
|
+
<div ref="stickyItemsContainerRef" class="sticky-items-container">
|
|
10
|
+
<div
|
|
11
|
+
class="sticky-item"
|
|
12
|
+
v-for="(item, key) in itemCount"
|
|
13
|
+
:key="key"
|
|
14
|
+
:style="{
|
|
15
|
+
'animation-timeline': key === itemCount - 1 ? 'none' : `--section-${timelineId}-${key}`,
|
|
16
|
+
'z-index': itemCount - key,
|
|
17
|
+
}"
|
|
18
|
+
ref="stickyItemsRef"
|
|
19
|
+
>
|
|
20
|
+
<slot :name="`stickyItem-${key}`"></slot>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<section
|
|
25
|
+
v-for="(item, key) in itemCount"
|
|
26
|
+
:key="key"
|
|
27
|
+
class="scrolling-section"
|
|
28
|
+
:style="{
|
|
29
|
+
'view-timeline-name': `--section-${timelineId}-${key}`,
|
|
30
|
+
}"
|
|
31
|
+
ref="scrollingItemsRef"
|
|
32
|
+
>
|
|
33
|
+
<slot :name="`scrollingItem-${key}`"></slot>
|
|
34
|
+
</section>
|
|
35
|
+
</component>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<script setup lang="ts">
|
|
39
|
+
const props = defineProps({
|
|
40
|
+
tag: {
|
|
41
|
+
type: String as PropType<"div" | "section" | "main" | "article" | "aside">,
|
|
42
|
+
default: "div",
|
|
43
|
+
validator: (val: string) => ["div", "section", "main", "article", "aside"].includes(val),
|
|
44
|
+
},
|
|
45
|
+
itemCount: {
|
|
46
|
+
type: Number as PropType<number>,
|
|
47
|
+
default: 0,
|
|
48
|
+
},
|
|
49
|
+
styleClassPassthrough: {
|
|
50
|
+
type: [String, Array] as PropType<string | string[]>,
|
|
51
|
+
default: () => [],
|
|
52
|
+
},
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const { elementClasses, resetElementClasses } = useStyleClassPassthrough(props.styleClassPassthrough)
|
|
56
|
+
|
|
57
|
+
const timelineId = useId()
|
|
58
|
+
const stickyItemsContainerRef = useTemplateRef<HTMLElement | null>("stickyItemsContainerRef")
|
|
59
|
+
const stickyItemsRef = useTemplateRef<HTMLElement[] | null>("stickyItemsRef")
|
|
60
|
+
const scrollContainerRef = useTemplateRef<HTMLElement | null>("scrollContainerRef")
|
|
61
|
+
const scrollingItemsRef = useTemplateRef<HTMLElement[] | null>("scrollingItemsRef")
|
|
62
|
+
const timelineInset = ref("35% 35%")
|
|
63
|
+
const topPercent = ref("0")
|
|
64
|
+
const bottomPercent = ref("0")
|
|
65
|
+
|
|
66
|
+
const timelineScope = computed(() =>
|
|
67
|
+
Array.from({ length: props.itemCount }, (_, i) => `--section-${timelineId}-${i}`).join(", ")
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
const calculateInset = () => {
|
|
71
|
+
if (!stickyItemsContainerRef.value) return
|
|
72
|
+
|
|
73
|
+
const rect = stickyItemsContainerRef.value.getBoundingClientRect()
|
|
74
|
+
const innerHeight = window.innerHeight
|
|
75
|
+
|
|
76
|
+
topPercent.value = ((rect.top / innerHeight) * 100).toFixed(2)
|
|
77
|
+
bottomPercent.value = (((innerHeight - rect.bottom) / innerHeight) * 100).toFixed(2)
|
|
78
|
+
|
|
79
|
+
timelineInset.value = `${topPercent.value}% ${bottomPercent.value}%`
|
|
80
|
+
|
|
81
|
+
if (!scrollContainerRef.value) return
|
|
82
|
+
scrollContainerRef.value.style.setProperty("--calculated-inset", timelineInset.value)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let requestAnimationFrameId: number | null = null
|
|
86
|
+
const onScrollDebounce = () => {
|
|
87
|
+
if (requestAnimationFrameId !== null) return
|
|
88
|
+
requestAnimationFrameId = requestAnimationFrame(() => {
|
|
89
|
+
calculateInset()
|
|
90
|
+
requestAnimationFrameId = null
|
|
91
|
+
})
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
watch(
|
|
95
|
+
() => props.styleClassPassthrough,
|
|
96
|
+
() => {
|
|
97
|
+
resetElementClasses(props.styleClassPassthrough)
|
|
98
|
+
}
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
const fallbackScrollHandler = () => {
|
|
102
|
+
const sections = scrollingItemsRef.value || []
|
|
103
|
+
const layers = stickyItemsRef.value || []
|
|
104
|
+
|
|
105
|
+
const mid = window.innerHeight / 2
|
|
106
|
+
|
|
107
|
+
sections.forEach((section, i) => {
|
|
108
|
+
const rect = section.getBoundingClientRect()
|
|
109
|
+
const active = rect.top <= mid && rect.bottom >= mid
|
|
110
|
+
if (layers[i]) layers[i].style.opacity = active ? "1" : "0"
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const supportsScrollTimeline = import.meta.client ? CSS.supports("animation-timeline: view()") : false
|
|
115
|
+
|
|
116
|
+
onMounted(() => {
|
|
117
|
+
calculateInset()
|
|
118
|
+
if (supportsScrollTimeline) {
|
|
119
|
+
window.addEventListener("scroll", onScrollDebounce)
|
|
120
|
+
} else {
|
|
121
|
+
window.addEventListener("scroll", fallbackScrollHandler)
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
onUnmounted(() => {
|
|
126
|
+
if (supportsScrollTimeline) {
|
|
127
|
+
window.removeEventListener("scroll", onScrollDebounce)
|
|
128
|
+
} else {
|
|
129
|
+
window.removeEventListener("scroll", fallbackScrollHandler)
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
</script>
|
|
133
|
+
|
|
134
|
+
<style lang="css">
|
|
135
|
+
.wipe-away-vertical {
|
|
136
|
+
.sticky-items-container {
|
|
137
|
+
position: sticky;
|
|
138
|
+
top: 50%;
|
|
139
|
+
left: 100%;
|
|
140
|
+
transform: translateY(-50%);
|
|
141
|
+
|
|
142
|
+
.sticky-item {
|
|
143
|
+
position: absolute;
|
|
144
|
+
inset: 0;
|
|
145
|
+
border-radius: 0.5rem;
|
|
146
|
+
width: 100%;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.scrolling-section {
|
|
151
|
+
view-timeline-axis: block;
|
|
152
|
+
view-timeline-inset: var(--calculated-inset);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@supports (animation-timeline: view()) {
|
|
157
|
+
@keyframes wipe-out {
|
|
158
|
+
0% {
|
|
159
|
+
clip-path: inset(0 0 0% 0);
|
|
160
|
+
}
|
|
161
|
+
100% {
|
|
162
|
+
clip-path: inset(0 0 100% 0);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.sticky-item {
|
|
167
|
+
animation: wipe-out 1s linear both;
|
|
168
|
+
animation-range: entry 0% entry 100%;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@supports not (animation-timeline: view()) {
|
|
173
|
+
.sticky-item {
|
|
174
|
+
opacity: 0;
|
|
175
|
+
transition: opacity 0.4s ease-in-out;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
</style>
|
package/package.json
CHANGED