Skip to content

Commit 70b9fa3

Browse files
committed
新的图片组件,支持 figuration
1 parent 984106a commit 70b9fa3

File tree

2 files changed

+189
-1
lines changed

2 files changed

+189
-1
lines changed

public/styles/global.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
/* @import url("https://chinese-fonts-cdn.deno.dev/packages/lywkpmydb/dist/LXGWWenKaiMonoScreen/result.css"); */
21
@import url("https://chinese-fonts-cdn.deno.dev/packages/GuanKiapTsingKhai/dist/GuanKiapTsingKhai/result.css");
32

43
body {

src/components/Figure.astro

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
// src/components/Figure.astro
3+
import { Image} from 'astro:assets';
4+
5+
type Layout = 'bottom-center' | 'bottom-left' | 'bottom-right' | 'top-hover' | 'left' | 'right';
6+
7+
interface Props {
8+
src: string;
9+
alt: string;
10+
width: number;
11+
height: number;
12+
class?: string;
13+
loading?: 'eager' | 'lazy';
14+
quality?: number | 'low' | 'mid' | 'high' | 'max';
15+
format?: 'avif' | 'png' | 'jpeg' | 'svg' | 'webp';
16+
figureClass?: string;
17+
layout?: Layout;
18+
zoomable?: boolean;
19+
}
20+
21+
const {
22+
figureClass,
23+
layout = 'bottom-center',
24+
zoomable = false,
25+
src,
26+
alt,
27+
width,
28+
height,
29+
class: className,
30+
loading,
31+
quality,
32+
format,
33+
} = Astro.props;
34+
35+
const isRowLayout = layout === 'left' || layout === 'right';
36+
---
37+
38+
<figure
39+
class:list={[
40+
'group',
41+
figureClass,
42+
{
43+
'relative': !isRowLayout,
44+
'flex items-center gap-4': isRowLayout,
45+
'flex-row-reverse': layout === 'left',
46+
'flex-row': layout === 'right',
47+
},
48+
]}
49+
data-zoomable={zoomable ? 'true' : undefined}
50+
>
51+
{isRowLayout ? (
52+
<div class="flex-shrink-0 w-1/2">
53+
<Image {src} {alt} {width} {height} class={className} {loading} {quality} {format} />
54+
</div>
55+
) : (
56+
<Image {src} {alt} {width} {height} class={className} {loading} {quality} {format} />
57+
)}
58+
59+
{Astro.slots.has('caption') && (
60+
<figcaption
61+
class:list={[
62+
'transition-all duration-300',
63+
{
64+
'mt-2 text-sm': layout.startsWith('bottom'),
65+
'text-center': layout === 'bottom-center',
66+
'text-left': layout === 'bottom-left',
67+
'text-right': layout === 'bottom-right',
68+
'flex-1': isRowLayout,
69+
'absolute inset-0 flex items-center justify-center p-4 bg-black/60 text-white opacity-0 group-hover:opacity-100': layout === 'top-hover',
70+
},
71+
]}
72+
>
73+
<slot name="caption" />
74+
</figcaption>
75+
)}
76+
</figure>
77+
78+
<script>
79+
function initializeImageViewer() {
80+
const figures = document.querySelectorAll('figure[data-zoomable="true"]');
81+
82+
figures.forEach(figure => {
83+
const img = figure.querySelector('img');
84+
if (img instanceof HTMLElement) {
85+
if (img.dataset.viewerInitialized) return;
86+
img.dataset.viewerInitialized = 'true';
87+
img.style.cursor = 'zoom-in';
88+
89+
img.addEventListener('click', () => openViewer(img as HTMLImageElement));
90+
}
91+
});
92+
93+
function openViewer(img: HTMLImageElement) {
94+
const overlay = document.createElement('div');
95+
overlay.id = 'image-viewer-overlay';
96+
97+
const content = document.createElement('img');
98+
content.id = 'image-viewer-content';
99+
content.src = img.src;
100+
101+
overlay.appendChild(content);
102+
document.body.appendChild(overlay);
103+
document.body.style.overflow = 'hidden'; // Keep this to prevent background scroll
104+
105+
let scale = 1;
106+
let isDragging = false;
107+
let startX = 0, startY = 0;
108+
let translateX = 0, translateY = 0;
109+
110+
const updateTransform = () => {
111+
content.style.transform = `translate(${translateX}px, ${translateY}px) scale(${scale})`;
112+
};
113+
114+
const wheelHandler = (e: WheelEvent) => {
115+
e.preventDefault();
116+
const scaleAmount = e.deltaY > 0 ? -0.1 : 0.1;
117+
scale = Math.max(0.5, Math.min(scale + scaleAmount, 5));
118+
updateTransform();
119+
};
120+
121+
const mouseDownHandler = (e: MouseEvent) => {
122+
e.preventDefault();
123+
isDragging = true;
124+
startX = e.clientX - translateX;
125+
startY = e.clientY - translateY;
126+
content.style.cursor = 'grabbing';
127+
};
128+
129+
const mouseMoveHandler = (e: MouseEvent) => {
130+
if (!isDragging) return;
131+
translateX = e.clientX - startX;
132+
translateY = e.clientY - startY;
133+
updateTransform();
134+
};
135+
136+
const mouseUpHandler = () => {
137+
isDragging = false;
138+
content.style.cursor = 'grab';
139+
};
140+
141+
const closeModal = () => {
142+
document.body.style.overflow = 'auto';
143+
overlay.remove();
144+
window.removeEventListener('mousemove', mouseMoveHandler);
145+
window.removeEventListener('mouseup', mouseUpHandler);
146+
};
147+
148+
overlay.addEventListener('click', closeModal);
149+
content.addEventListener('click', (e) => e.stopPropagation());
150+
overlay.addEventListener('wheel', wheelHandler);
151+
content.addEventListener('mousedown', mouseDownHandler);
152+
window.addEventListener('mousemove', mouseMoveHandler);
153+
window.addEventListener('mouseup', mouseUpHandler);
154+
}
155+
}
156+
157+
initializeImageViewer();
158+
document.addEventListener('astro:after-swap', initializeImageViewer);
159+
</script>
160+
161+
<style is:global>
162+
#image-viewer-overlay {
163+
position: fixed;
164+
inset: 0;
165+
z-index: 9999;
166+
display: flex;
167+
align-items: center;
168+
justify-content: center;
169+
padding: 2rem;
170+
background-color: rgba(255, 255, 255, 0.85);
171+
backdrop-filter: blur(8px);
172+
/* Removed transition for opacity */
173+
}
174+
175+
html.dark #image-viewer-overlay {
176+
background-color: rgba(10, 10, 10, 0.85);
177+
}
178+
179+
#image-viewer-content {
180+
max-width: 100%;
181+
max-height: 100%;
182+
object-fit: contain;
183+
border-radius: 8px;
184+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
185+
transform-origin: center center;
186+
transition: transform 0.15s linear;
187+
cursor: grab;
188+
}
189+
</style>

0 commit comments

Comments
 (0)