diff --git a/backend/routes/albums.js b/backend/routes/albums.js
index bbc0bcb..9840652 100644
--- a/backend/routes/albums.js
+++ b/backend/routes/albums.js
@@ -12,17 +12,18 @@ async function getAlbumDetails(album) {
);
album.tracks = tracks;
- // 티저 이미지 조회
+ // 티저 이미지 조회 (3개 해상도 URL 포함)
const [teasers] = await pool.query(
- "SELECT image_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order",
+ "SELECT original_url, medium_url, thumb_url FROM album_teasers WHERE album_id = ? ORDER BY sort_order",
[album.id]
);
- album.teasers = teasers.map((t) => t.image_url);
+ album.teasers = teasers;
- // 컨셉 포토 조회 (멤버 정보 포함)
+ // 컨셉 포토 조회 (멤버 정보 + 3개 해상도 URL + 크기 정보 포함)
const [photos] = await pool.query(
`SELECT
- p.id, p.photo_url, p.photo_type, p.concept_name, p.sort_order,
+ p.id, p.original_url, p.medium_url, p.thumb_url, p.photo_type, p.concept_name, p.sort_order,
+ p.width, p.height,
GROUP_CONCAT(m.name ORDER BY m.id SEPARATOR ', ') as members
FROM album_photos p
LEFT JOIN album_photo_members pm ON p.id = pm.photo_id
@@ -42,7 +43,11 @@ async function getAlbumDetails(album) {
}
conceptPhotos[concept].push({
id: photo.id,
- url: photo.photo_url,
+ original_url: photo.original_url,
+ medium_url: photo.medium_url,
+ thumb_url: photo.thumb_url,
+ width: photo.width,
+ height: photo.height,
type: photo.photo_type,
members: photo.members,
sortOrder: photo.sort_order,
diff --git a/frontend/src/pages/pc/AlbumDetail.jsx b/frontend/src/pages/pc/AlbumDetail.jsx
index 6b626e0..39fd0e9 100644
--- a/frontend/src/pages/pc/AlbumDetail.jsx
+++ b/frontend/src/pages/pc/AlbumDetail.jsx
@@ -110,18 +110,7 @@ function AlbumDetail() {
});
}, [name]);
- // URL을 썸네일/원본 버전으로 변환하는 헬퍼
- const getThumbUrl = (url) => {
- const parts = url.split('/');
- const filename = parts.pop();
- return [...parts, 'thumb_400', filename].join('/');
- };
-
- const getOriginalUrl = (url) => {
- const parts = url.split('/');
- const filename = parts.pop();
- return [...parts, 'original', filename].join('/');
- };
+ // URL 헬퍼 함수는 더 이상 필요 없음 - API에서 직접 제공
// 날짜 포맷팅
const formatDate = (dateStr) => {
@@ -248,11 +237,11 @@ function AlbumDetail() {
{album.teasers.map((teaser, index) => (
setLightbox({ open: true, images: album.teasers.map(t => getOriginalUrl(t)), index })}
+ onClick={() => setLightbox({ open: true, images: album.teasers.map(t => t.original_url), index })}
className="w-24 h-24 bg-gray-200 rounded-lg overflow-hidden cursor-pointer transition-all duration-200 hover:scale-110 hover:shadow-xl hover:z-10"
>
})
@@ -357,11 +346,11 @@ function AlbumDetail() {
{previewPhotos.map((photo, idx) => (
setLightbox({ open: true, images: [getOriginalUrl(photo.url)], index: 0 })}
+ onClick={() => setLightbox({ open: true, images: [photo.original_url], index: 0 })}
className="aspect-square bg-gray-200 rounded-xl overflow-hidden cursor-pointer transition-all duration-200 hover:scale-105 hover:shadow-xl hover:z-10"
>
})
diff --git a/frontend/src/pages/pc/AlbumGallery.jsx b/frontend/src/pages/pc/AlbumGallery.jsx
index e6bc633..75bff93 100644
--- a/frontend/src/pages/pc/AlbumGallery.jsx
+++ b/frontend/src/pages/pc/AlbumGallery.jsx
@@ -5,6 +5,26 @@ import { X, ChevronLeft, ChevronRight, Download } from 'lucide-react';
import { RowsPhotoAlbum } from 'react-photo-album';
import 'react-photo-album/rows.css';
+// CSS로 호버 효과 추가 + overflow 문제 수정
+const galleryStyles = `
+.react-photo-album {
+ overflow: visible !important;
+}
+.react-photo-album--row {
+ overflow: visible !important;
+}
+.react-photo-album--photo {
+ transition: transform 0.3s ease, filter 0.3s ease !important;
+ cursor: pointer;
+ overflow: visible !important;
+}
+.react-photo-album--photo:hover {
+ transform: scale(1.05);
+ filter: brightness(0.9);
+ z-index: 10;
+}
+`;
+
function AlbumGallery() {
const { name } = useParams();
const navigate = useNavigate();
@@ -15,57 +35,28 @@ function AlbumGallery() {
const [imageLoaded, setImageLoaded] = useState(false);
const [slideDirection, setSlideDirection] = useState(0);
- // URL을 썸네일/원본 버전으로 변환하는 헬퍼
- const getThumbUrl = (url) => {
- // https://s3.../photo/01.webp → https://s3.../photo/thumb_400/01.webp
- const parts = url.split('/');
- const filename = parts.pop();
- return [...parts, 'thumb_400', filename].join('/');
- };
-
- const getOriginalUrl = (url) => {
- const parts = url.split('/');
- const filename = parts.pop();
- return [...parts, 'original', filename].join('/');
- };
-
- // 이미지 dimensions 로드
- const loadImageDimensions = (url) => {
- return new Promise((resolve) => {
- const img = new Image();
- img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
- img.onerror = () => resolve({ width: 3, height: 4 }); // 기본 3:4 비율
- img.src = url;
- });
- };
-
useEffect(() => {
fetch(`/api/albums/by-name/${name}`)
.then(res => res.json())
- .then(async data => {
+ .then(data => {
setAlbum(data);
const allPhotos = [];
if (data.conceptPhotos && typeof data.conceptPhotos === 'object') {
Object.entries(data.conceptPhotos).forEach(([concept, photos]) => {
photos.forEach(p => allPhotos.push({
- thumbUrl: getThumbUrl(p.url),
- originalUrl: getOriginalUrl(p.url),
+ // API에서 직접 제공하는 URL 및 크기 정보 사용
+ mediumUrl: p.medium_url,
+ originalUrl: p.original_url,
+ width: p.width || 800,
+ height: p.height || 1200,
title: concept,
members: p.members ? p.members.split(', ') : []
}));
});
}
- // 모든 이미지 dimensions 로드
- const photosWithDimensions = await Promise.all(
- allPhotos.map(async (photo) => {
- const dims = await loadImageDimensions(photo.thumbUrl);
- return { ...photo, width: dims.width, height: dims.height };
- })
- );
-
- setPhotos(photosWithDimensions);
+ setPhotos(allPhotos);
setLoading(false);
})
.catch(error => {
@@ -201,22 +192,25 @@ function AlbumGallery() {
{photos.length}장의 사진
- {/* Justified 갤러리 - react-photo-album */}
+ {/* CSS 스타일 주입 */}
+
+
+ {/* Justified 갤러리 - 동적 비율 + 호버 */}
({
- src: photo.thumbUrl,
- width: photo.width || 300,
- height: photo.height || 400,
+ src: photo.mediumUrl,
+ width: photo.width || 800,
+ height: photo.height || 1200,
key: idx.toString()
}))}
- targetRowHeight={300}
+ targetRowHeight={280}
spacing={8}
+ rowConstraints={{ singleRowMaxHeight: 400, minPhotos: 1 }}
onClick={({ index }) => openLightbox(index)}
componentsProps={{
- container: { style: { cursor: 'pointer' } },
image: {
loading: 'lazy',
- style: { borderRadius: '8px', transition: 'transform 0.3s' }
+ style: { borderRadius: '12px' }
}
}}
/>