@vue-lynx-example/swiper 0.1.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.
- package/README.md +21 -0
- package/dist/static/image/1.282abf96.png +0 -0
- package/dist/static/image/2.e0b3d9f4.png +0 -0
- package/dist/static/image/3.3b83fa8b.png +0 -0
- package/dist/static/image/4.df45ef86.png +0 -0
- package/dist/static/image/5.719e0d42.png +0 -0
- package/dist/static/image/6.dbe87dd5.png +0 -0
- package/dist/static/image/7.66ac96a5.png +0 -0
- package/dist/static/image/8.93f167b4.png +0 -0
- package/dist/swiper-empty.lynx.bundle +0 -0
- package/dist/swiper-empty.web.bundle +1 -0
- package/dist/swiper-mts.lynx.bundle +0 -0
- package/dist/swiper-mts.web.bundle +1 -0
- package/dist/swiper.lynx.bundle +0 -0
- package/dist/swiper.web.bundle +1 -0
- package/lynx.config.ts +22 -0
- package/package.json +34 -0
- package/src/Components/Indicator.vue +21 -0
- package/src/Components/NavBar.vue +16 -0
- package/src/Components/Page.vue +32 -0
- package/src/Components/SafeArea.vue +10 -0
- package/src/Components/SwiperItem.vue +12 -0
- package/src/Swiper/Swiper.vue +63 -0
- package/src/Swiper/index.ts +23 -0
- package/src/Swiper/useOffset.ts +93 -0
- package/src/Swiper/useUpdateSwiperStyle.ts +21 -0
- package/src/SwiperEmpty/Swiper.vue +25 -0
- package/src/SwiperEmpty/index.ts +17 -0
- package/src/SwiperMTS/Swiper.vue +64 -0
- package/src/SwiperMTS/index.ts +17 -0
- package/src/assets/1.png +0 -0
- package/src/assets/2.png +0 -0
- package/src/assets/3.png +0 -0
- package/src/assets/4.png +0 -0
- package/src/assets/5.png +0 -0
- package/src/assets/6.png +0 -0
- package/src/assets/7.png +0 -0
- package/src/assets/8.png +0 -0
- package/src/assets/back.png +0 -0
- package/src/assets/heart.png +0 -0
- package/src/assets/star.png +0 -0
- package/src/shims-vue.d.ts +11 -0
- package/src/swiper.css +144 -0
- package/src/utils/const.ts +15 -0
- package/src/utils/pics.ts +21 -0
- package/src/utils/useAnimate.ts +106 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { useMainThreadRef } from 'vue-lynx';
|
|
3
|
+
import SwiperItem from '../Components/SwiperItem.vue';
|
|
4
|
+
|
|
5
|
+
declare const SystemInfo: { pixelWidth: number; pixelRatio: number };
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
data: string[];
|
|
9
|
+
itemWidth?: number;
|
|
10
|
+
}>(), {
|
|
11
|
+
itemWidth: () => SystemInfo.pixelWidth / SystemInfo.pixelRatio,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// --- Main Thread refs ---
|
|
15
|
+
const containerRef = useMainThreadRef<unknown>(null);
|
|
16
|
+
const currentOffsetRef = useMainThreadRef<number>(0);
|
|
17
|
+
const touchStartXRef = useMainThreadRef<number>(0);
|
|
18
|
+
const touchStartOffsetRef = useMainThreadRef<number>(0);
|
|
19
|
+
|
|
20
|
+
// --- MTS touch handlers ---
|
|
21
|
+
const handleTouchStart = (e: { touches: Array<{ clientX: number }> }) => {
|
|
22
|
+
'main thread';
|
|
23
|
+
touchStartXRef.current = e.touches[0].clientX;
|
|
24
|
+
touchStartOffsetRef.current = currentOffsetRef.current;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const handleTouchMove = (e: { touches: Array<{ clientX: number }> }) => {
|
|
28
|
+
'main thread';
|
|
29
|
+
const delta = e.touches[0].clientX - touchStartXRef.current;
|
|
30
|
+
const offset = touchStartOffsetRef.current + delta;
|
|
31
|
+
currentOffsetRef.current = offset;
|
|
32
|
+
const el = containerRef as unknown as {
|
|
33
|
+
current?: { setStyleProperty?(k: string, v: string): void };
|
|
34
|
+
};
|
|
35
|
+
if (el.current?.setStyleProperty) {
|
|
36
|
+
el.current.setStyleProperty('transform', `translateX(${offset}px)`);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const handleTouchEnd = () => {
|
|
41
|
+
'main thread';
|
|
42
|
+
touchStartXRef.current = 0;
|
|
43
|
+
touchStartOffsetRef.current = 0;
|
|
44
|
+
};
|
|
45
|
+
</script>
|
|
46
|
+
|
|
47
|
+
<template>
|
|
48
|
+
<view class="swiper-wrapper">
|
|
49
|
+
<view
|
|
50
|
+
class="swiper-container"
|
|
51
|
+
:main-thread-ref="containerRef"
|
|
52
|
+
:main-thread-bindtouchstart="handleTouchStart"
|
|
53
|
+
:main-thread-bindtouchmove="handleTouchMove"
|
|
54
|
+
:main-thread-bindtouchend="handleTouchEnd"
|
|
55
|
+
>
|
|
56
|
+
<SwiperItem
|
|
57
|
+
v-for="(pic, index) in data"
|
|
58
|
+
:key="index"
|
|
59
|
+
:pic="pic"
|
|
60
|
+
:item-width="props.itemWidth"
|
|
61
|
+
/>
|
|
62
|
+
</view>
|
|
63
|
+
</view>
|
|
64
|
+
</template>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createApp, defineComponent, h } from 'vue-lynx';
|
|
2
|
+
|
|
3
|
+
import '../swiper.css';
|
|
4
|
+
import Swiper from './Swiper.vue';
|
|
5
|
+
import Page from '../Components/Page.vue';
|
|
6
|
+
import { picsArr } from '../utils/pics.js';
|
|
7
|
+
|
|
8
|
+
const App = defineComponent({
|
|
9
|
+
setup() {
|
|
10
|
+
return () =>
|
|
11
|
+
h(Page, null, {
|
|
12
|
+
default: () => h(Swiper, { data: picsArr }),
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
createApp(App).mount();
|
package/src/assets/1.png
ADDED
|
Binary file
|
package/src/assets/2.png
ADDED
|
Binary file
|
package/src/assets/3.png
ADDED
|
Binary file
|
package/src/assets/4.png
ADDED
|
Binary file
|
package/src/assets/5.png
ADDED
|
Binary file
|
package/src/assets/6.png
ADDED
|
Binary file
|
package/src/assets/7.png
ADDED
|
Binary file
|
package/src/assets/8.png
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/swiper.css
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/* Safe Area */
|
|
2
|
+
.safe-area {
|
|
3
|
+
width: 100%;
|
|
4
|
+
height: 100%;
|
|
5
|
+
padding-bottom: 20px;
|
|
6
|
+
display: flex;
|
|
7
|
+
flex-direction: column;
|
|
8
|
+
background-color: #000;
|
|
9
|
+
}
|
|
10
|
+
.safe-area.android {
|
|
11
|
+
padding-bottom: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* NavBar */
|
|
15
|
+
.nav-bar {
|
|
16
|
+
width: 100%;
|
|
17
|
+
height: 48px;
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
position: relative;
|
|
21
|
+
justify-content: space-between;
|
|
22
|
+
background: black;
|
|
23
|
+
}
|
|
24
|
+
.nav-bar .left-icon {
|
|
25
|
+
width: 24px;
|
|
26
|
+
height: 24px;
|
|
27
|
+
margin-left: 20px;
|
|
28
|
+
}
|
|
29
|
+
.nav-bar .right-icon {
|
|
30
|
+
width: 24px;
|
|
31
|
+
height: 24px;
|
|
32
|
+
margin-right: 27px;
|
|
33
|
+
}
|
|
34
|
+
.nav-bar .nav-title {
|
|
35
|
+
position: absolute;
|
|
36
|
+
width: 100%;
|
|
37
|
+
text-align: center;
|
|
38
|
+
font-size: 20px;
|
|
39
|
+
font-weight: 500;
|
|
40
|
+
color: white;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Page */
|
|
44
|
+
.page-container {
|
|
45
|
+
flex: 1;
|
|
46
|
+
width: 100%;
|
|
47
|
+
padding-bottom: 30px;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
align-items: center;
|
|
51
|
+
background-color: #000;
|
|
52
|
+
}
|
|
53
|
+
.card-detail-container {
|
|
54
|
+
position: absolute;
|
|
55
|
+
width: 100%;
|
|
56
|
+
background: linear-gradient(
|
|
57
|
+
180deg,
|
|
58
|
+
rgba(17, 17, 19, 0.00) 29.47%,
|
|
59
|
+
#111113 99.97%
|
|
60
|
+
);
|
|
61
|
+
bottom: 75px;
|
|
62
|
+
}
|
|
63
|
+
.card-detail {
|
|
64
|
+
background-color: #282e38;
|
|
65
|
+
border-radius: 12px;
|
|
66
|
+
width: calc(100% - 40px);
|
|
67
|
+
margin: 0 20px 18px 20px;
|
|
68
|
+
padding: 21px;
|
|
69
|
+
}
|
|
70
|
+
.card-detail-title {
|
|
71
|
+
display: flex;
|
|
72
|
+
justify-content: space-between;
|
|
73
|
+
align-items: center;
|
|
74
|
+
margin-bottom: 15px;
|
|
75
|
+
}
|
|
76
|
+
.card-detail-title-price {
|
|
77
|
+
font-size: 24px;
|
|
78
|
+
color: #ff6740;
|
|
79
|
+
}
|
|
80
|
+
.card-detail-amount {
|
|
81
|
+
color: rgba(255, 255, 255, 0.85);
|
|
82
|
+
text-align: right;
|
|
83
|
+
font-size: 12px;
|
|
84
|
+
}
|
|
85
|
+
.card-detail-desc {
|
|
86
|
+
display: flex;
|
|
87
|
+
justify-content: space-between;
|
|
88
|
+
align-items: center;
|
|
89
|
+
}
|
|
90
|
+
.card-detail-desc-text {
|
|
91
|
+
font-size: 17px;
|
|
92
|
+
color: rgba(255, 255, 255, 0.85);
|
|
93
|
+
}
|
|
94
|
+
.order-button {
|
|
95
|
+
background-color: #ff6740;
|
|
96
|
+
border-radius: 8px;
|
|
97
|
+
width: calc(100% - 40px);
|
|
98
|
+
margin: 0 20px;
|
|
99
|
+
padding: 13px 0;
|
|
100
|
+
display: flex;
|
|
101
|
+
justify-content: center;
|
|
102
|
+
align-items: center;
|
|
103
|
+
}
|
|
104
|
+
.order-text {
|
|
105
|
+
font-size: 17px;
|
|
106
|
+
color: #fff;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* Indicator */
|
|
110
|
+
.indicator {
|
|
111
|
+
position: absolute;
|
|
112
|
+
bottom: 140px;
|
|
113
|
+
left: 50%;
|
|
114
|
+
transform: translateX(-50%);
|
|
115
|
+
display: flex;
|
|
116
|
+
justify-content: center;
|
|
117
|
+
align-items: center;
|
|
118
|
+
}
|
|
119
|
+
.indicator .indicator-item {
|
|
120
|
+
width: 10px;
|
|
121
|
+
height: 10px;
|
|
122
|
+
border-radius: 50%;
|
|
123
|
+
margin: 0 5px;
|
|
124
|
+
background-color: rgba(255, 255, 255, 0.5);
|
|
125
|
+
}
|
|
126
|
+
.indicator .indicator-item.active {
|
|
127
|
+
background-color: #ff6740;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/* Swiper */
|
|
131
|
+
.swiper-wrapper {
|
|
132
|
+
flex: 1;
|
|
133
|
+
width: 100%;
|
|
134
|
+
}
|
|
135
|
+
.swiper-container {
|
|
136
|
+
display: linear;
|
|
137
|
+
linear-orientation: horizontal;
|
|
138
|
+
height: 100%;
|
|
139
|
+
}
|
|
140
|
+
.swiper-item {
|
|
141
|
+
display: flex;
|
|
142
|
+
align-items: flex-end;
|
|
143
|
+
justify-content: center;
|
|
144
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import pic1 from '../assets/1.png';
|
|
2
|
+
import pic2 from '../assets/2.png';
|
|
3
|
+
import pic3 from '../assets/3.png';
|
|
4
|
+
import pic4 from '../assets/4.png';
|
|
5
|
+
import pic5 from '../assets/5.png';
|
|
6
|
+
import pic6 from '../assets/6.png';
|
|
7
|
+
import pic7 from '../assets/7.png';
|
|
8
|
+
import pic8 from '../assets/8.png';
|
|
9
|
+
|
|
10
|
+
export const pics = [
|
|
11
|
+
{ src: pic1, width: 511, height: 437 },
|
|
12
|
+
{ src: pic2, width: 1024, height: 1589 },
|
|
13
|
+
{ src: pic3, width: 510, height: 418 },
|
|
14
|
+
{ src: pic4, width: 509, height: 438 },
|
|
15
|
+
{ src: pic5, width: 1024, height: 1557 },
|
|
16
|
+
{ src: pic6, width: 509, height: 415 },
|
|
17
|
+
{ src: pic7, width: 509, height: 426 },
|
|
18
|
+
{ src: pic8, width: 1024, height: 1544 },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const picsArr = pics.slice(0, 8).map((pic) => pic.src);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { useMainThreadRef } from 'vue-lynx';
|
|
2
|
+
|
|
3
|
+
export interface AnimationOptions {
|
|
4
|
+
from: number;
|
|
5
|
+
to: number;
|
|
6
|
+
duration?: number;
|
|
7
|
+
delay?: number;
|
|
8
|
+
easing?: (t: number) => number;
|
|
9
|
+
onUpdate?: (value: number) => void;
|
|
10
|
+
onComplete?: () => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Common easing functions
|
|
14
|
+
export const easings = {
|
|
15
|
+
linear: (t: number) => {
|
|
16
|
+
'main thread';
|
|
17
|
+
return t;
|
|
18
|
+
},
|
|
19
|
+
easeInQuad: (t: number) => {
|
|
20
|
+
'main thread';
|
|
21
|
+
return t * t;
|
|
22
|
+
},
|
|
23
|
+
easeOutQuad: (t: number) => {
|
|
24
|
+
'main thread';
|
|
25
|
+
return 1 - (1 - t) * (1 - t);
|
|
26
|
+
},
|
|
27
|
+
easeInOutQuad: (t: number) => {
|
|
28
|
+
'main thread';
|
|
29
|
+
return t < 0.5 ? 2 * t * t : 1 - Math.pow(-2 * t + 2, 2) / 2;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function animateInner(options: AnimationOptions) {
|
|
34
|
+
'main thread';
|
|
35
|
+
const {
|
|
36
|
+
from,
|
|
37
|
+
to,
|
|
38
|
+
duration = 5000,
|
|
39
|
+
delay = 0,
|
|
40
|
+
easing = easings.easeInOutQuad,
|
|
41
|
+
onUpdate,
|
|
42
|
+
onComplete,
|
|
43
|
+
} = options;
|
|
44
|
+
|
|
45
|
+
let startTs = 0;
|
|
46
|
+
let rafId = 0;
|
|
47
|
+
|
|
48
|
+
function tick(ts: number) {
|
|
49
|
+
const progress =
|
|
50
|
+
Math.max(Math.min(((ts - startTs - delay) * 100) / duration, 100), 0)
|
|
51
|
+
/ 100;
|
|
52
|
+
|
|
53
|
+
const easedProgress = easing(progress);
|
|
54
|
+
const currentValue = from + (to - from) * easedProgress;
|
|
55
|
+
onUpdate?.(currentValue);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function updateRafId(id: number) {
|
|
59
|
+
rafId = id;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function step(ts: number) {
|
|
63
|
+
if (!startTs) {
|
|
64
|
+
startTs = Number(ts);
|
|
65
|
+
}
|
|
66
|
+
// make sure progress can reach 100%
|
|
67
|
+
if (ts - startTs <= duration + 100) {
|
|
68
|
+
tick(ts);
|
|
69
|
+
updateRafId(requestAnimationFrame(step));
|
|
70
|
+
} else {
|
|
71
|
+
onComplete?.();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
updateRafId(requestAnimationFrame(step));
|
|
76
|
+
|
|
77
|
+
function cancel() {
|
|
78
|
+
cancelAnimationFrame(rafId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
cancel,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function useAnimate() {
|
|
87
|
+
const lastCancelRef = useMainThreadRef<(() => void) | null>(null);
|
|
88
|
+
|
|
89
|
+
function cancel() {
|
|
90
|
+
'main thread';
|
|
91
|
+
lastCancelRef.current?.();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function animate(options: AnimationOptions) {
|
|
95
|
+
'main thread';
|
|
96
|
+
cancel();
|
|
97
|
+
|
|
98
|
+
const { cancel: innerCancel } = animateInner(options);
|
|
99
|
+
lastCancelRef.current = innerCancel;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
cancel,
|
|
104
|
+
animate,
|
|
105
|
+
};
|
|
106
|
+
}
|