@rxdrag/website-lib 0.0.4 → 0.0.7
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/index.ts +4 -0
- package/package.json +15 -17
- package/src/components/AnimationNumber.astro +104 -0
- package/src/components/AttachmentIcon.astro +45 -0
- package/src/components/Flip.astro +53 -0
- package/src/components/FlipLogic.ts +242 -0
- package/src/components/Link.astro +81 -0
- package/src/components/LinkLogic.ts +51 -0
- package/src/components/Meta.astro +66 -0
- package/src/components/Modal.astro +69 -0
- package/src/components/ModalCloser.astro +22 -0
- package/src/components/ModalPanel.astro +22 -0
- package/src/components/ModalTrigger.astro +30 -0
- package/src/components/Motion.astro +333 -0
- package/src/components/MotionTypes.ts +71 -0
- package/src/components/Popover.astro +57 -0
- package/src/components/Popup.astro +19 -0
- package/src/components/PopverPanel.astro +22 -0
- package/src/components/RichTextView.astro +34 -0
- package/src/components/Tabs.astro +124 -0
- package/src/components/TabsBody.astro +19 -0
- package/src/components/TabsHeader.astro +19 -0
- package/src/components/TabsLogic.ts +10 -0
- package/src/components/TabsPanel.astro +17 -0
- package/src/components/TabsTab.astro +18 -0
- package/src/components/index.ts +19 -0
- package/src/env.d.ts +1 -0
- package/src/index.ts +2 -0
- package/src/lib/formatDate.ts +15 -0
- package/src/lib/index.ts +2 -0
- package/src/lib/pagination.ts +114 -0
- package/dist/_astro/client.D-vx6DWE.js +0 -170
package/index.ts
ADDED
package/package.json
CHANGED
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rxdrag/website-lib",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": "./dist/index.mjs",
|
|
6
|
-
"module": "./dist/index.mjs",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
5
|
"exports": {
|
|
9
|
-
".":
|
|
10
|
-
"import": "./dist/index.mjs",
|
|
11
|
-
"types": "./dist/index.d.ts"
|
|
12
|
-
}
|
|
6
|
+
".": "./index.ts"
|
|
13
7
|
},
|
|
14
8
|
"files": [
|
|
15
|
-
"
|
|
9
|
+
"src",
|
|
10
|
+
"index.ts"
|
|
11
|
+
],
|
|
12
|
+
"keywords": [
|
|
13
|
+
"astro-component"
|
|
16
14
|
],
|
|
17
15
|
"publishConfig": {
|
|
18
16
|
"access": "public"
|
|
19
17
|
},
|
|
20
18
|
"devDependencies": {
|
|
21
19
|
"@astrojs/react": "^3.0.0",
|
|
22
|
-
"@astrojs/vercel": "^8.1.2",
|
|
23
20
|
"@babel/types": "^7.26.9",
|
|
24
21
|
"@types/react": "^18.2.22",
|
|
25
22
|
"@types/react-dom": "^18.2.7",
|
|
@@ -28,21 +25,22 @@
|
|
|
28
25
|
"typescript": "^5",
|
|
29
26
|
"@rxdrag/entify-hooks": "0.2.39",
|
|
30
27
|
"@rxdrag/slate-preview": "1.2.54",
|
|
31
|
-
"@rxdrag/
|
|
32
|
-
"@rxdrag/
|
|
28
|
+
"@rxdrag/tsconfig": "0.2.0",
|
|
29
|
+
"@rxdrag/eslint-config-custom": "0.2.11"
|
|
33
30
|
},
|
|
34
31
|
"dependencies": {
|
|
35
|
-
"add": "^2.0.6",
|
|
36
32
|
"clsx": "^2.1.0",
|
|
37
33
|
"motion": "^12.4.7",
|
|
38
34
|
"react": "^18.2.0",
|
|
39
35
|
"react-dom": "^18.2.0",
|
|
40
|
-
"@rxdrag/
|
|
41
|
-
"@rxdrag/
|
|
36
|
+
"@rxdrag/rxcms-models": "0.3.45",
|
|
37
|
+
"@rxdrag/website-lib-core": "0.0.6"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"astro": "^4.0.0 || ^5.0.0"
|
|
42
41
|
},
|
|
43
42
|
"scripts": {
|
|
44
43
|
"lint": "eslint . --ext .ts,tsx",
|
|
45
|
-
"clean": "rimraf -rf .turbo && rimraf -rf node_modules
|
|
46
|
-
"build": "astro build"
|
|
44
|
+
"clean": "rimraf -rf .turbo && rimraf -rf node_modules"
|
|
47
45
|
}
|
|
48
46
|
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { AnimationOptions } from "motion";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
start?: number;
|
|
6
|
+
end?: number;
|
|
7
|
+
transition?: AnimationOptions;
|
|
8
|
+
class?: string;
|
|
9
|
+
sign?: "string";
|
|
10
|
+
once?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
start = 0,
|
|
15
|
+
end = 100,
|
|
16
|
+
transition = { duration: 2 },
|
|
17
|
+
class: className,
|
|
18
|
+
sign = "+",
|
|
19
|
+
once = false,
|
|
20
|
+
} = Astro.props;
|
|
21
|
+
|
|
22
|
+
const motionValue = {
|
|
23
|
+
start,
|
|
24
|
+
end,
|
|
25
|
+
transition,
|
|
26
|
+
};
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
<div
|
|
30
|
+
data-animation-number={JSON.stringify(motionValue)}
|
|
31
|
+
data-animation-number-once={once}
|
|
32
|
+
class:list={["flex items-center justify-center gap-2", className]}
|
|
33
|
+
>
|
|
34
|
+
<span class="motion-safe:animate-number">{start}</span>
|
|
35
|
+
{
|
|
36
|
+
sign && (
|
|
37
|
+
<span class="motion-safe:animate-sign" style="display: none;">
|
|
38
|
+
{sign}
|
|
39
|
+
</span>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<script>
|
|
45
|
+
import { onEverySwap, onPageLoaded } from "@rxdrag/website-lib-core";
|
|
46
|
+
import { animate, inView } from "motion";
|
|
47
|
+
|
|
48
|
+
function initAnimation() {
|
|
49
|
+
const elements = document.querySelectorAll("[data-animation-number]");
|
|
50
|
+
elements.forEach((element) => {
|
|
51
|
+
if (element instanceof HTMLElement) {
|
|
52
|
+
if (element.dataset.animationNumber) {
|
|
53
|
+
const data = JSON.parse(element.dataset.animationNumber);
|
|
54
|
+
// 确保 start 和 end 是数字类型
|
|
55
|
+
const startValue = Number(data.start);
|
|
56
|
+
const endValue = Number(data.end);
|
|
57
|
+
const transition = data.transition;
|
|
58
|
+
const once = element.dataset.animationNumberOnce === "true";
|
|
59
|
+
|
|
60
|
+
const numberElement = element.querySelector(
|
|
61
|
+
".motion-safe\\:animate-number"
|
|
62
|
+
) as HTMLElement;
|
|
63
|
+
const signElement = element.querySelector(
|
|
64
|
+
".motion-safe\\:animate-sign"
|
|
65
|
+
) as HTMLElement;
|
|
66
|
+
|
|
67
|
+
inView(numberElement, () => {
|
|
68
|
+
const aleadyRun =
|
|
69
|
+
numberElement.dataset.animationNumberRun === "true";
|
|
70
|
+
if (aleadyRun && once) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
numberElement.dataset.animationNumberRun = "true";
|
|
74
|
+
animate(startValue, endValue, {
|
|
75
|
+
...transition,
|
|
76
|
+
onUpdate: (latest) =>
|
|
77
|
+
(numberElement.innerHTML = Math.round(latest)?.toString()),
|
|
78
|
+
onComplete: () => {
|
|
79
|
+
if (signElement) {
|
|
80
|
+
signElement.style.display = "inline";
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
return () => {
|
|
85
|
+
if (!once) {
|
|
86
|
+
numberElement.innerHTML = startValue.toString();
|
|
87
|
+
if (signElement) {
|
|
88
|
+
signElement.style.display = "none";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
onPageLoaded(() => {
|
|
99
|
+
initAnimation();
|
|
100
|
+
});
|
|
101
|
+
onEverySwap(() => {
|
|
102
|
+
initAnimation();
|
|
103
|
+
});
|
|
104
|
+
</script>
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
---
|
|
2
|
+
const { extName, ...rest } = Astro.props;
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<div {...rest}>
|
|
6
|
+
{
|
|
7
|
+
extName && extName === ".pdf" ? (
|
|
8
|
+
<svg
|
|
9
|
+
fill="currentColor"
|
|
10
|
+
viewBox="0 0 1024 1024"
|
|
11
|
+
version="1.1"
|
|
12
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
13
|
+
>
|
|
14
|
+
<path d="M905.185809 178.844158C898.576738 172.685485 891.19337 165.824412 883.21687 158.436127 860.422682 137.322863 837.434925 116.207791 815.697647 96.487895 813.243072 94.261877 813.243072 94.261877 810.786411 92.037081 781.783552 65.781062 757.590948 44.376502 739.713617 29.293612 729.254178 20.469111 721.020606 13.860686 714.970549 9.501727 710.955023 6.608611 707.690543 4.524745 704.47155 2.998714 700.417679 1.07689 696.638044-0.094029 691.307277 0.005928 677.045677 0.273349 665.6 11.769337 665.6 26.182727L665.6 77.352844 665.6 128.522961 665.6 230.863194 665.6 256.448252 691.2 256.448252 896 256.448252 870.4 230.863194 870.4 998.414942 896 972.829884 230.381436 972.829884C187.90385 972.829884 153.6 938.623723 153.6 896.20663L153.6 26.182727 128 51.767786 588.8 51.767786C602.93849 51.767786 614.4 40.312965 614.4 26.182727 614.4 12.05249 602.93849 0.597669 588.8 0.597669L128 0.597669 102.4 0.597669 102.4 26.182727 102.4 896.20663C102.4 966.91021 159.652833 1024 230.381436 1024L896 1024 921.6 1024 921.6 998.414942 921.6 230.863194 921.6 205.278135 896 205.278135 691.2 205.278135 716.8 230.863194 716.8 128.522961 716.8 77.352844 716.8 26.182727C716.8 39.813762 705.748075 50.91427 692.267725 51.167041 687.705707 51.252584 685.069822 50.435995 682.52845 49.231204 682.259458 49.103682 683.344977 49.796618 685.029451 51.010252 689.779394 54.432502 697.145822 60.34494 706.686383 68.394196 724.009052 83.009121 747.816448 104.072869 776.413589 129.961594 778.850014 132.168064 778.850014 132.168064 781.285216 134.376514 802.876774 153.964212 825.739479 174.96442 848.413564 195.966437 856.350957 203.3185 863.697005 210.144893 870.269888 216.269843 874.209847 219.941299 877.019309 222.565641 878.499674 223.951409 888.81866 233.610931 905.019017 233.081212 914.684179 222.768247 924.349344 212.455283 923.819315 196.264383 913.500326 186.604861 911.981323 185.182945 909.155025 182.542876 905.185809 178.844158ZM102.4 461.128719 0 461.128719 0 896.074709 512 896.074709 1024 896.074709 1024 461.128719 153.6 461.128719 153.6 460.531049 102.4 460.531049 102.4 461.128719ZM208.2 711 208.2 819.2 157.6 819.2 157.6 528 269 528C301.533495 528 327.366571 536.466581 346.5 553.4 365.633429 570.333419 375.2 592.733195 375.2 620.6 375.2 649.133476 365.833427 671.333254 347.1 687.2 328.366573 703.066746 302.133502 711 268.4 711L208.2 711ZM208.2 670.4 269 670.4C287.00009 670.4 300.733286 666.166709 310.2 657.7 319.666714 649.233291 324.4 637.000079 324.4 621 324.4 605.266588 319.600047 592.700047 310 583.3 300.399951 573.899953 287.200083 569.066669 270.4 568.8L208.2 568.8 208.2 670.4ZM419.4 819.2 419.4 528 505.4 528C531.133461 528 553.966566 533.733276 573.9 545.2 593.833434 556.666724 609.266611 572.933229 620.2 594 631.133389 615.066771 636.6 639.199863 636.6 666.4L636.6 681C636.6 708.600139 631.100055 732.866562 620.1 753.8 609.099945 774.733438 593.433436 790.866609 573.1 802.2 552.766564 813.533391 529.466799 819.2 503.2 819.2L419.4 819.2ZM470 568.8 470 778.8 503 778.8C529.533466 778.8 549.89993 770.500083 564.1 753.9 578.30007 737.299917 585.533331 713.466822 585.8 682.4L585.8 666.2C585.8 634.599842 578.933402 610.46675 565.2 593.8 551.466598 577.13325 531.533463 568.8 505.4 568.8L470 568.8ZM854.8 695.8 737.6 695.8 737.6 819.2 687 819.2 687 528 872 528 872 568.8 737.6 568.8 737.6 655.4 854.8 655.4 854.8 695.8Z" />
|
|
15
|
+
</svg>
|
|
16
|
+
) : extName && (extName === ".xls" || extName === ".xlsx") ? (
|
|
17
|
+
<svg
|
|
18
|
+
fill="currentColor"
|
|
19
|
+
viewBox="0 0 1024 1024"
|
|
20
|
+
version="1.1"
|
|
21
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
22
|
+
>
|
|
23
|
+
<path d="M810.18 949.8H213.821c-20.551 0-37.271-16.649-37.271-37.1V111.3c0-20.45 16.72-37.1 37.271-37.1h395.27v143.74c0 52.25 46.97 94.77 104.71 94.77h133.65V912.7c0 20.451-16.72 37.1-37.27 37.1zM678.9 118.68l130.85 130.85H713.8c-19.25 0-34.9-14.169-34.9-31.59v-99.26z m168.551 55.41l-99.89-99.89L673.36 0H213.82C152.16 0 102 49.93 102 111.3v801.4c0 61.371 50.16 111.3 111.821 111.3h596.36C871.84 1024 922 974.071 922 912.7V248.64l-74.55-74.55z" />
|
|
24
|
+
<path d="M810.18 949.8H213.821c-20.551 0-37.271-16.649-37.271-37.1V111.3c0-20.45 16.72-37.1 37.271-37.1h395.27v143.74c0 52.25 46.97 94.77 104.71 94.77h133.65V912.7c0 20.451-16.72 37.1-37.27 37.1zM678.9 118.68l130.85 130.85H713.8c-19.25 0-34.9-14.169-34.9-31.59v-99.26z m168.551 55.41l-99.89-99.89L673.36 0H213.82C152.16 0 102 49.93 102 111.3v801.4c0 61.371 50.16 111.3 111.821 111.3h596.36C871.84 1024 922 974.071 922 912.7V248.64l-74.55-74.55z" />
|
|
25
|
+
<path d="M559.714 579.272L668.11 404.855a0.2 0.2 0 0 0-0.17-0.305h-89.12a0.2 0.2 0 0 0-0.174 0.102l-67.604 120.3-70.265-120.303a0.2 0.2 0 0 0-0.173-0.1h-85.779a0.2 0.2 0 0 0-0.168 0.308L461.02 571.401 347.653 746.24a0.2 0.2 0 0 0 0.168 0.309h88.007a0.2 0.2 0 0 0 0.17-0.095l74.362-120.51 75.153 120.51a0.2 0.2 0 0 0 0.17 0.095h89.835a0.2 0.2 0 0 0 0.164-0.314L559.714 579.272z" />
|
|
26
|
+
</svg>
|
|
27
|
+
) : extName && (extName === ".doc" || extName === ".docx") ? (
|
|
28
|
+
<svg
|
|
29
|
+
fill="currentColor"
|
|
30
|
+
viewBox="0 0 1024 1024"
|
|
31
|
+
version="1.1"
|
|
32
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
33
|
+
>
|
|
34
|
+
<path d="M854.6 288.6L639.4 73.4c-6-6-14.1-9.4-22.6-9.4H192c-17.7 0-32 14.3-32 32v832c0 17.7 14.3 32 32 32h640c17.7 0 32-14.3 32-32V311.3c0-8.5-3.4-16.7-9.4-22.7zM790.2 326H602V137.8L790.2 326z m1.8 562H232V136h302v216c0 23.2 18.8 42 42 42h216v494zM528.1 472h-32.2c-5.5 0-10.3 3.7-11.6 9.1L434.6 680l-46.1-198.7c-1.3-5.4-6.1-9.3-11.7-9.3h-35.4c-1.1 0-2.1 0.1-3.1 0.4-6.4 1.7-10.2 8.3-8.5 14.7l74.2 276c1.4 5.2 6.2 8.9 11.6 8.9h32c5.4 0 10.2-3.6 11.6-8.9l52.8-197 52.8 197c1.4 5.2 6.2 8.9 11.6 8.9h31.8c5.4 0 10.2-3.6 11.6-8.9l74.4-276c0.3-1 0.4-2.1 0.4-3.1 0-6.6-5.4-12-12-12H647c-5.6 0-10.4 3.9-11.7 9.3l-45.8 199.1-49.8-199.3c-1.3-5.4-6.1-9.1-11.6-9.1z" />
|
|
35
|
+
</svg>
|
|
36
|
+
) : (
|
|
37
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
|
38
|
+
<path
|
|
39
|
+
fill="currentColor"
|
|
40
|
+
d="M14 11a3 3 0 0 1-3-3V4H7a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-8zm-2-3a2 2 0 0 0 2 2h3.59L12 4.41zM7 3h5l7 7v9a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3V6a3 3 0 0 1 3-3"
|
|
41
|
+
/>
|
|
42
|
+
</svg>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
</div>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { HTMLElementsWithChildren } from "./MotionTypes";
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
flipKey?: string;
|
|
6
|
+
as?: HTMLElementsWithChildren;
|
|
7
|
+
class?: string;
|
|
8
|
+
style?: string | Record<string, string | number>;
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { flipKey, as = "div", class: className, style, ...rest } = Astro.props;
|
|
13
|
+
|
|
14
|
+
const Element = as;
|
|
15
|
+
const mergedStyle = {
|
|
16
|
+
...style,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// 将 animate 属性序列化为 JSON 字符串
|
|
20
|
+
const flipProps = {
|
|
21
|
+
"data-flip": flipKey || true,
|
|
22
|
+
};
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
<Element class={className} style={mergedStyle} {...flipProps} {...rest}>
|
|
26
|
+
<slot />
|
|
27
|
+
</Element>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
/* 确保动画过程中元素的行为正常 */
|
|
31
|
+
[data-flip] {
|
|
32
|
+
will-change: transform;
|
|
33
|
+
transform-origin: 0 0;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
|
|
37
|
+
<script>
|
|
38
|
+
import { onEverySwap, onPageLoaded } from "@rxdrag/website-lib-core";
|
|
39
|
+
import { FlipController } from "./FlipLogic";
|
|
40
|
+
|
|
41
|
+
const initFlip = () => {
|
|
42
|
+
FlipController.getInstance().init();
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// 在页面加载和每次页面转场后都初始化Flip
|
|
46
|
+
onPageLoaded(() => {
|
|
47
|
+
initFlip();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
onEverySwap(() => {
|
|
51
|
+
initFlip();
|
|
52
|
+
});
|
|
53
|
+
</script>
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { DATA_MOTION_FLIP } from "./TabsLogic";
|
|
2
|
+
|
|
3
|
+
// 全局 Flip 控制器
|
|
4
|
+
export class FlipController {
|
|
5
|
+
// 单例实例
|
|
6
|
+
private static instance: FlipController;
|
|
7
|
+
|
|
8
|
+
// 记录元素的上一个位置(相对于文档的绝对位置)
|
|
9
|
+
private previousPositions = new Map<string, {
|
|
10
|
+
left: number,
|
|
11
|
+
top: number,
|
|
12
|
+
width: number,
|
|
13
|
+
height: number
|
|
14
|
+
}>();
|
|
15
|
+
// 标记正在处理的元素,防止递归循环
|
|
16
|
+
private processingElements = new Set<HTMLElement>();
|
|
17
|
+
// 观察器
|
|
18
|
+
private resizeObserver: ResizeObserver | null = null;
|
|
19
|
+
|
|
20
|
+
// 获取单例实例
|
|
21
|
+
static getInstance(): FlipController {
|
|
22
|
+
if (!FlipController.instance) {
|
|
23
|
+
FlipController.instance = new FlipController();
|
|
24
|
+
}
|
|
25
|
+
return FlipController.instance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 初始化控制器
|
|
29
|
+
init() {
|
|
30
|
+
// 设置观察器
|
|
31
|
+
this.setupObservers();
|
|
32
|
+
|
|
33
|
+
// 初始化元素
|
|
34
|
+
this.initElements();
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// 设置观察器
|
|
38
|
+
private setupObservers() {
|
|
39
|
+
// 设置 ResizeObserver 监控元素尺寸变化
|
|
40
|
+
this.resizeObserver = new ResizeObserver((entries) => {
|
|
41
|
+
entries.forEach((entry) => {
|
|
42
|
+
if (entry.target instanceof HTMLElement) {
|
|
43
|
+
this.performAnimation(entry.target);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// 监听滚动事件 - 滚动不会影响相对于文档的绝对位置,所以不需要特殊处理
|
|
49
|
+
window.addEventListener('scroll', () => {
|
|
50
|
+
// 页面滚动时,重新计算所有需要动画的元素
|
|
51
|
+
document.querySelectorAll<HTMLElement>("[data-flip]").forEach(element => {
|
|
52
|
+
if (this.previousPositions.has(element.dataset.flip || "")) {
|
|
53
|
+
this.performAnimation(element);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}, { passive: true });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// 初始化元素,记录初始位置
|
|
60
|
+
private initElements() {
|
|
61
|
+
// 查找所有带有 data-flip 属性的元素
|
|
62
|
+
const flipElements = document.querySelectorAll<HTMLElement>("[data-flip]");
|
|
63
|
+
flipElements.forEach((element) => {
|
|
64
|
+
// 添加到 ResizeObserver 监控
|
|
65
|
+
if (this.resizeObserver) {
|
|
66
|
+
this.resizeObserver.observe(element);
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// 延迟记录初始位置,确保元素已经渲染完成
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
// 记录每个元素的初始位置
|
|
73
|
+
flipElements.forEach((element) => {
|
|
74
|
+
this.recordPosition(element);
|
|
75
|
+
});
|
|
76
|
+
}, 100); // 给予足够的时间让元素渲染完成
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 记录元素位置
|
|
80
|
+
private recordPosition(element: HTMLElement) {
|
|
81
|
+
const flipKey = element.dataset.flip;
|
|
82
|
+
if (!flipKey) return;
|
|
83
|
+
|
|
84
|
+
if (!this.isVisible(element)) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 获取元素相对于视口的位置
|
|
89
|
+
const rect = element.getBoundingClientRect();
|
|
90
|
+
|
|
91
|
+
// 计算元素相对于文档的绝对位置
|
|
92
|
+
const absoluteLeft = rect.left + window.scrollX;
|
|
93
|
+
const absoluteTop = rect.top + window.scrollY;
|
|
94
|
+
|
|
95
|
+
// 记录元素相对于文档的绝对位置
|
|
96
|
+
this.previousPositions.set(flipKey, {
|
|
97
|
+
left: absoluteLeft,
|
|
98
|
+
top: absoluteTop,
|
|
99
|
+
width: rect.width,
|
|
100
|
+
height: rect.height
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 检查元素是否可见
|
|
105
|
+
private isVisible(element: HTMLElement): boolean {
|
|
106
|
+
const rect = element.getBoundingClientRect();
|
|
107
|
+
return (
|
|
108
|
+
rect.width > 0 &&
|
|
109
|
+
rect.height > 0 &&
|
|
110
|
+
rect.top < window.innerHeight &&
|
|
111
|
+
rect.bottom > 0 &&
|
|
112
|
+
rect.left < window.innerWidth &&
|
|
113
|
+
rect.right > 0
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 执行 FLIP 动画
|
|
118
|
+
performAnimation(element: HTMLElement) {
|
|
119
|
+
// 防止重复处理同一元素
|
|
120
|
+
if (this.processingElements.has(element)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 获取元素的 flip key
|
|
125
|
+
const flipKey = element.dataset.flip;
|
|
126
|
+
if (!flipKey) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// 检查元素是否可见
|
|
131
|
+
if (!this.isVisible(element)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 记录当前正在处理的元素
|
|
136
|
+
this.processingElements.add(element);
|
|
137
|
+
|
|
138
|
+
// 获取元素的当前位置(相对于视口)
|
|
139
|
+
const currentRect = element.getBoundingClientRect();
|
|
140
|
+
|
|
141
|
+
// 检查当前位置是否有效
|
|
142
|
+
if (currentRect.width === 0 || currentRect.height === 0) {
|
|
143
|
+
this.processingElements.delete(element);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// 计算当前元素相对于文档的绝对位置
|
|
148
|
+
const currentAbsoluteLeft = currentRect.left + window.scrollX;
|
|
149
|
+
const currentAbsoluteTop = currentRect.top + window.scrollY;
|
|
150
|
+
|
|
151
|
+
// 获取元素的上一个位置
|
|
152
|
+
const previousPosition = this.previousPositions.get(flipKey);
|
|
153
|
+
|
|
154
|
+
// 如果没有上一个位置,记录当前位置并返回
|
|
155
|
+
if (!previousPosition) {
|
|
156
|
+
this.recordPosition(element);
|
|
157
|
+
this.processingElements.delete(element);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// 检查上一个位置是否有效
|
|
162
|
+
if (previousPosition.width === 0 || previousPosition.height === 0) {
|
|
163
|
+
this.recordPosition(element);
|
|
164
|
+
this.processingElements.delete(element);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 计算位置差异(基于绝对位置,不需要考虑滚动)
|
|
169
|
+
const deltaX = previousPosition.left - currentAbsoluteLeft;
|
|
170
|
+
const deltaY = previousPosition.top - currentAbsoluteTop;
|
|
171
|
+
const scaleX = previousPosition.width / currentRect.width;
|
|
172
|
+
const scaleY = previousPosition.height / currentRect.height;
|
|
173
|
+
|
|
174
|
+
// 如果位置没有变化,跳过动画
|
|
175
|
+
if (
|
|
176
|
+
Math.abs(deltaX) < 1 &&
|
|
177
|
+
Math.abs(deltaY) < 1 &&
|
|
178
|
+
Math.abs(scaleX - 1) < 0.01 &&
|
|
179
|
+
Math.abs(scaleY - 1) < 0.01
|
|
180
|
+
) {
|
|
181
|
+
this.recordPosition(element);
|
|
182
|
+
this.processingElements.delete(element);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
try {
|
|
187
|
+
// 使用原生 Web Animation API 执行动画
|
|
188
|
+
|
|
189
|
+
// 先将元素设置为初始位置
|
|
190
|
+
element.style.transform = `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`;
|
|
191
|
+
element.style.transformOrigin = "0 0";
|
|
192
|
+
|
|
193
|
+
// 使用 requestAnimationFrame 确保初始状态已应用
|
|
194
|
+
requestAnimationFrame(() => {
|
|
195
|
+
const container = element.closest(`[${DATA_MOTION_FLIP}]`);
|
|
196
|
+
const transionJSON = (container as HTMLElement | null)?.getAttribute(
|
|
197
|
+
DATA_MOTION_FLIP
|
|
198
|
+
);
|
|
199
|
+
const transion = transionJSON
|
|
200
|
+
? JSON.parse(transionJSON)
|
|
201
|
+
: {
|
|
202
|
+
duration: 300,
|
|
203
|
+
};
|
|
204
|
+
// 创建动画
|
|
205
|
+
const animation = element.animate(
|
|
206
|
+
[
|
|
207
|
+
{
|
|
208
|
+
transform: `translate(${deltaX}px, ${deltaY}px) scale(${scaleX}, ${scaleY})`,
|
|
209
|
+
},
|
|
210
|
+
{ transform: "translate(0, 0) scale(1, 1)" },
|
|
211
|
+
],
|
|
212
|
+
transion
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
// 动画完成后的处理
|
|
216
|
+
animation.onfinish = () => {
|
|
217
|
+
// 清除样式并更新位置
|
|
218
|
+
element.style.transform = "";
|
|
219
|
+
element.style.transformOrigin = "";
|
|
220
|
+
this.recordPosition(element);
|
|
221
|
+
this.processingElements.delete(element);
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
// 设置一个备用的超时处理,以防动画事件不触发
|
|
225
|
+
setTimeout(() => {
|
|
226
|
+
if (this.processingElements.has(element)) {
|
|
227
|
+
element.style.transform = "";
|
|
228
|
+
element.style.transformOrigin = "";
|
|
229
|
+
this.recordPosition(element);
|
|
230
|
+
this.processingElements.delete(element);
|
|
231
|
+
}
|
|
232
|
+
}, 350); // 略长于动画时间
|
|
233
|
+
});
|
|
234
|
+
} catch (error) {
|
|
235
|
+
// 出错时也要清理
|
|
236
|
+
element.style.transform = "";
|
|
237
|
+
element.style.transformOrigin = "";
|
|
238
|
+
this.recordPosition(element);
|
|
239
|
+
this.processingElements.delete(element);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { LinkType, toHref } from "./LinkLogic";
|
|
3
|
+
import { IMotionProps } from "./MotionTypes";
|
|
4
|
+
import Motion from "./Motion.astro";
|
|
5
|
+
|
|
6
|
+
// 定义a标签属性的接口
|
|
7
|
+
interface AnchorAttributes {
|
|
8
|
+
target?: "_blank" | "_self" | "_parent" | "_top";
|
|
9
|
+
rel?: string;
|
|
10
|
+
download?: boolean | string;
|
|
11
|
+
ping?: string;
|
|
12
|
+
referrerpolicy?: string;
|
|
13
|
+
hreflang?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface Props extends IMotionProps, AnchorAttributes {
|
|
17
|
+
type?: LinkType | string;
|
|
18
|
+
to?: string;
|
|
19
|
+
class?: string;
|
|
20
|
+
//用于active类名的传递
|
|
21
|
+
activeWhen?: string | true;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { type, to, class: className, activeWhen, ...props } = Astro.props;
|
|
25
|
+
const href = toHref(type, to);
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
<Motion
|
|
29
|
+
as="a"
|
|
30
|
+
data-actived-path={activeWhen}
|
|
31
|
+
href={href}
|
|
32
|
+
class={className}
|
|
33
|
+
{...props}
|
|
34
|
+
>
|
|
35
|
+
<slot />
|
|
36
|
+
</Motion>
|
|
37
|
+
|
|
38
|
+
<script>
|
|
39
|
+
import { onEverySwap, onPageLoaded } from "@rxdrag/website-lib-core";
|
|
40
|
+
|
|
41
|
+
const initLinks = () => {
|
|
42
|
+
// 获取当前URL的路径部分(去除域名)
|
|
43
|
+
const currentPath = window.location.pathname;
|
|
44
|
+
|
|
45
|
+
// 获取所有导航链接
|
|
46
|
+
const links = document.querySelectorAll<HTMLAnchorElement>(
|
|
47
|
+
"[data-actived-path]"
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
links.forEach((anchorLink) => {
|
|
51
|
+
// 从链接URL中提取路径部分
|
|
52
|
+
try {
|
|
53
|
+
const linkUrl = new URL(anchorLink.href);
|
|
54
|
+
const linkPath =
|
|
55
|
+
anchorLink.dataset.activedPath === "true"
|
|
56
|
+
? linkUrl.pathname
|
|
57
|
+
: anchorLink.dataset.activedPath;
|
|
58
|
+
|
|
59
|
+
// 检查当前路径是否与链接路径匹配或是其子路径
|
|
60
|
+
if (
|
|
61
|
+
currentPath === linkPath ||
|
|
62
|
+
(linkPath !== "/" && linkPath && currentPath.startsWith(linkPath))
|
|
63
|
+
) {
|
|
64
|
+
anchorLink.classList.add("actived");
|
|
65
|
+
} else {
|
|
66
|
+
anchorLink.classList.remove("actived");
|
|
67
|
+
}
|
|
68
|
+
} catch (e) {
|
|
69
|
+
// 处理无效URL的情况
|
|
70
|
+
console.warn("Invalid URL:", e, anchorLink);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
onEverySwap(() => {
|
|
76
|
+
initLinks();
|
|
77
|
+
});
|
|
78
|
+
onPageLoaded(() => {
|
|
79
|
+
initLinks();
|
|
80
|
+
});
|
|
81
|
+
</script>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export enum LinkType {
|
|
2
|
+
Product = "product",
|
|
3
|
+
Post = "post",
|
|
4
|
+
Page = "page",
|
|
5
|
+
User = "user",
|
|
6
|
+
ProductCategory = "product-category",
|
|
7
|
+
PostCategory = "post-category",
|
|
8
|
+
Link = "link",
|
|
9
|
+
Products = "proudcts",
|
|
10
|
+
Posts = "posts",
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function toHref(type: LinkType | string, to: string) {
|
|
14
|
+
if (type === LinkType.Link || !type) {
|
|
15
|
+
return to;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (type === LinkType.Products) {
|
|
19
|
+
return "/products/page/1";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (type === LinkType.Posts) {
|
|
23
|
+
return "/posts/page/1";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (type === LinkType.Page) {
|
|
27
|
+
return `/${to}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (type === LinkType.Post) {
|
|
31
|
+
return `/posts/${to}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (type === LinkType.Product) {
|
|
35
|
+
return `/products/${to}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (type === LinkType.User) {
|
|
39
|
+
return `/profiles/${to}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (type === LinkType.ProductCategory) {
|
|
43
|
+
return `/products/categories/${to}/1`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (type === LinkType.PostCategory) {
|
|
47
|
+
return `/posts/categories/${to}/1`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return `/${type}/${to}`;
|
|
51
|
+
}
|