با Astro یک گالری تصویر بسازید و واکنش نشان دهید

مقدمه
من می خواستم یک صفحه گالری ساده و شبیه به اینستاگرام ، در وب سایت داشته باشم که بتوانم عکس های روزمره خود را به اشتراک بگذارم. در ابتدا من آن را با استفاده از بسته Benhowell/React-Grid-Gallery برای گالری و Frontend-Collective/React-Image-Lightbox برای مؤلفه Lightbox پیاده سازی کردم. خوب کار کرد ، اما از آنجا که این بسته های میراث کمی است ، من نتوانستم به React 19 ارتقاء دهم ، تمام تصاویر را یکباره بدون صفحه بندی پیمایش بارگذاری کرد و نمره فانوس دریایی چندان عالی نبود.
اگر در تاریخ GIT E0165B حرکت کنید ، می توانید آن اجرای را مشاهده کنید:
# in git history navigate back to the old gallery commit
git checkout e0165b295db2ccc72bbbb7be4bdd7eb48f7dedae
# preview
yarn clean && yarn install && yarn dev
من تصمیم گرفتم که آن را مجدداً انجام دهم ، یک تحقیق سریع انجام دادم و تصمیم گرفتم که مؤلفه گالری خودم را بسازم و از بسته Dimsemenov/Photoswipe برای Lightbox استفاده کنم. و اینگونه است که این مقاله ایجاد شد ، در حالی که اجرای آن را در مورد مهمترین و جالب ترین بخش ها از این روند یادداشت کردم. به آن نگاه کنید که لزوماً بهترین راه برای تهیه گالری تصویر با Astro و React نیست بلکه به عنوان یکی از راه هایی که در عمل اثبات شده و به خوبی کار می کند.
آنچه ما خواهیم ساخت
https://www.youtube.com/watch؟v=biywybfjaxi
تصویر – مؤلفه سرور ، مؤلفه مشتری ، شکاف ، غرفه ها
این اولین معضل و تصمیم اولیه است که تمام کد آینده را که می نویسیم تأثیر می گذارد. از آنجا که این یک مثال وب سایت استاتیک است ، ما به طور طبیعی تمایل داریم که در زمان ساخت و ساز بتوانیم از قبل تهیه کنیم ، اما آیا این می تواند برای تصاویر نیز کار کند؟
Astro فراهم می کند
مؤلفه و این یک جزء سرور مانند سایر مؤلفه های Astro است. واضح است که ما نیاز خواهیم داشت onLoad
با onClick
رویدادهای روی یک تصویر و رویدادها در یک مؤلفه سرور امکان پذیر نیست. بله ، اما شاید ما بتوانیم از بسته بندی مؤلفه مشتری استفاده کنیم و Astro را عبور دهیم
مؤلفه به عنوان یک شکاف ، بنابراین ما می توانیم از هر دو مؤلفه Astro برای بهینه سازی تصویر و a استفاده کنیم for events, could this work?
Not really, for any preload effects onLoad
رویداد باید در
برچسب ، اما مهمتر این است که ما نمی توانیم هیچ یک از مشتری های مشتری را به شکاف منتقل کنیم
مؤلفه ، ما می توانیم در زمان ساخت فقط یک نمونه واحد ایجاد کنیم. برای هر مقادیر غرفه ، ما باید HTML تصویر جداگانه ای را از قبل تولید کنیم که در این حالت بسیار غیر عملی است.
نتیجه گیری: ما از یک مؤلفه مشتری React استفاده خواهیم کرد که از تعامل و ASTRO پشتیبانی می کند getImage()
عملکردی برای بهینه سازی تصاویر.
مسیر API در مقابل import.meta.glob()
ما می خواهیم به دلایل عملکرد و استقرار مناسب به یک وب سایت استاتیک بچسبیم. برای انتقال URL های تصویر به مشتری از چه راهی باید استفاده کنیم؟ ما می توانیم یک نقطه پایانی استاتیک ایجاد کنیم که به آرایه JSON خدمت می کند. ما حتی می توانیم یک نقطه انتهایی API پارامتری ایجاد کنیم که تصاویر بهینه سازی شده را ارائه می دهد.
بلافاصله ، چرا داشتن یک تماس اضافی HTTP برای JSON در مشتری وقتی می توانیم URL های تصویر را در زمان ساخت از قبل تولید کنیم ، این چیزی نیست که ما می خواهیم.
برای یک نقطه پایانی API استاتیک ، از آنجا که استاتیک است ، ما باید همه پارامترها را در زمان ساخت پیش تهیه کنیم ، بنابراین می توانیم این کار را انجام دهیم http://localhost/api/gallery/xl/image1.webp
اما نه http://localhost/api/gallery/300x200/image1.webp
وت http://localhost/api/gallery/301x200/image1.webp
، برای این کار ما نیاز به فعال کردن سمت سرور Astro داریم و Node.js را در زمان تولید قرار می دهیم.
اگر وارد شویم src
ویژگی یک تصویر وارداتی در حالت dev و prod ما چیزی شبیه به این خواهیم دید:
// in dev
http://localhost:3000/_image?href=/@fs/home/username/Desktop/nemanjam.github.io/src/assets/images/all-images/morning1.jpg?origWidth=4608&origHeight=2592&origFormat=jpg&w=1280&h=720&f=webp
// in prod
http://localhost:3000/_astro/morning1.CEdGhKb3_nVk9T.webp
بنابراین Astro در حال حاضر در حال ارائه تصاویر برای ما است ، با یک نقطه انتهایی API اختصاصی ، ما فقط می خواهیم بازنویسی URL دوستانه انسانی را انجام دهیم ، این فقط در صورتی که برخی از سرویس های خارجی آن تصاویر را بدست آورند ، که ما در اینجا نداریم ، می تواند مفید باشد.
نتیجه گیری: ما استفاده خواهیم کرد import.meta.glob('/src/assets/images/all-images/*.jpg')
از Vite گرفته تا وارد کردن تصاویر به عنوان ماژول برای به دست آوردن تصاویر در زمان ساخت و انتقال آنها به عنوان غرفه ها به مؤلفه گالری.
کد به شرح زیر SRC/LIBS/GALLERY/IMAGE.TS#L16 است:
// src/libs/gallery/images.ts
export const getGalleryImagesMetadata = (): ImageMetadata[] => {
const imageModules = import.meta.glob<{ default: ImageMetadata }>(
// can't be a variable
'/src/assets/images/all-images/*.jpg',
{ eager: true }
);
// convert map to array
const imagesMetadata = Object.keys(imageModules)
// filter excluded filenames
.filter((path) => !EXCLUDE_IMAGES.some((excludedFileName) => path.endsWith(excludedFileName)))
// return metadata array
.map((path) => imageModules[path].default);
return imagesMetadata;
};
ساخت رمز
ما کد را مانند این ساختار خواهیم داد: MDX (gallery.mdx) -> Astro component (Gallery.astro) -> React component (Gallery.jsx)
بشر پشته تماس از بالا به پایین است ، MDX یک لایه ارائه اعلامیه است ، مؤلفه Astro داده ها را برطرف می کند – تصاویر ، مؤلفه React رویدادها را کنترل می کند و منطق را تعریف می کند ، این پیچیده ترین لایه است.
کد (پاراگراف):
// src/pages/gallery.mdx
<Gallery class="not-prose grow" />
// src/components/Gallery.astro
<ReactGallery client:only="react" images={randomizedGalleryImages} />
// src/components/react/Gallery.tsx
<div className="grid grid-cols-1 gap-1 sm:grid-cols-2 lg:grid-cols-3">
{loadedImages.map((image) => (
<img {...imageProps} />
)}
</div>
نسل استاتیک ، شامل URL های تصویر و map()
روی مشتری
باز هم ، نکته جالب و مهم که فراموش می شود این است images.map()
برای داشتن صفحه بندی پیمایش بی نهایت ، باید در مؤلفه React باشد. برای این همه URL های تصویر (و سایر غرفه ها) نیاز به بسته بندی و در دسترس مشتری دارند ، که به عنوان غرفه های Astro به مؤلفه React منتقل می شود.
اگر قرار دادیم images.map()
در مؤلفه Astro ما می خواهیم یک لیست تصویر واحد مانند بدون هیچ گونه تعامل (صفحه بندی در پیمایش) داشته باشیم.
یادآوری: استاتیک “پس زمینه” فقط یک بار – در زمان ساخت اجرا می شود. ما فقط یک Node.js Runtime داریم و نه در تولید – در آنجا فقط یک پوشه استاتیک Weberver برای ارائه دارایی داریم. نوع واضح است ، اما وقتی تصمیم می گیریم کد خاصی را در یک سرور یا مؤلفه مشتری قرار دهیم ، گاهی اوقات نادیده گرفته می شود.
تصاویر پاسخگو ، بهینه شده – getImage()
وت
Astro تابع GetImage () را ارائه می دهد که ما برای بهینه سازی تصاویر و تولید از آنها استفاده خواهیم کرد
ویژگی های برچسب برای مشتری. این همان استدلال ها را می پذیرد
مؤلفه توجه داشته باشید ،
پشتیبانی از برچسب srcset
وت sizes
ویژگی هایی برای تصاویر پاسخگو که برای مورد استفاده ما کافی است. این بار ما نیازی نداریم
پشتیبانی از تصاویر مختلف (جهت هنری) و قالب های مختلف.
ما از پیش تنظیمات مختلف تصویر (اندازه) در SRC/LIBS/GALLERY/TRANSFORM.TS#L7 تهیه خواهیم کرد:
توجه داشته باشید که فقط تصویر کوچک از تصویر پاسخگو استفاده می کند ، و Lightbox از یک تصویر با اندازه ثابت استفاده می کند زیرا Photoswipe Lightbox از تصویر پاسخگو پشتیبانی نمی کند (حداقل بدون یک مؤلفه سفارشی).
// src/libs/gallery/transform.ts
// common props
const defaultAstroImageOptions = {
format: 'webp',
};
// thumbnail preset
export const thumbnailImageOptions = {
...IMAGE_SIZES.RESPONSIVE.GALLERY_THUMBNAIL,
};
// lightbox preset
export const lightboxImageOptions = {
...IMAGE_SIZES.FIXED.MDX_2XL_16_9,
};
// getImage() wrapper
export const getCustomImage = async (options: UnresolvedImageTransform): Promise<GetImageResult> =>
getImage({
...defaultAstroImageOptions,
...options,
});
بعد از آن ما استفاده می کنیم getCustomImage()
برای بهینه سازی تصاویر گالری که قبلاً با آنها بارگذاری شده بودیم import.meta.glob()
در SRC/libs/گالری/تصاویر. TTS#L50:
// src/libs/gallery/images.ts
export const getGalleryImages = async (): Promise<GalleryImage[]> => {
const thumbnails = await getCustomImages(thumbnailImageOptions);
const lightBoxes = await getCustomImages(lightboxImageOptions);
const galleryImages = mergeArrays(thumbnails, lightBoxes).map(([thumbnail, lightbox]) => ({
thumbnail: imageResultToImageAttributes(thumbnail),
lightbox: imageResultToImageAttributes(lightbox),
}));
return galleryImages;
};
// select only needed attributes for the
tag
export const imageResultToImageAttributes = (imageResult: GetImageResult): ImgTagAttributes => ({
src: imageResult.src,
srcSet: imageResult.srcSet?.attribute,
...imageResult.attributes,
});
اکنون ما آماده هستیم
ویژگی ها (Props) در دسترس برای عبور از مؤلفه مشتری Gallery React.
بخش جالب پیکربندی است sizes
(sizes
وت widths
استدلال در getImage()
) ویژگی برای تصاویر پاسخگو در SRC/Constants/Image.ts#L86:
// src/constants/image.ts
GALLERY_THUMBNAIL: {
widths: [TW_SCREENS.XS, TW_SCREENS.SM],
sizes: `(max-width: ${TW_SCREENS.SM}px) ${TW_SCREENS.SM}px, ${TW_SCREENS.XS}px`,
},
// actual
tag attributes that are generated with the GALLERY_THUMBNAIL
<img
sizes="(max-width: 640px) 640px, 475px"
srcset="
/_astro/river16.CcFOUvED_Z2d5kbP.webp 475w,
/_astro/river16.CcFOUvED_Z16pb6L.webp 640w
"
src="/_astro/river16.CcFOUvED_Z1Dswo2.webp"
width="4000"
height="2252"
/>
// src/components/react/Gallery.tsx
<div
id={GALLERY_ID}
className="pswp-gallery grid grid-cols-1 gap-1 sm:grid-cols-2 lg:grid-cols-3"
>
...
</div>
اگر با تعریف تصاویر پاسخگو آشنا نیستید ، آنطور که به نظر می رسد پیچیده نیست. کد فوق اساساً می گوید ، زیر SM
نقطه شکست صفحه (640px
) استفاده کنید SM
اندازه (عرض) (640px
) تصویر ، و اگر صفحه گسترده تر از آن است SM
از کوچکتر استفاده کنید XS
(475px
) تصویر. شاید استفاده از تصویر کوچکتر برای صفحه بزرگتر غیر منتظره باشد ، اما وقتی به شبکه پاسخگو که برای طرح گالری استفاده می شود ، منطقی است.
شما می توانید در کلاس های شبکه ای مشاهده کنید که زیر آن است sm:
تصویر Breakpoint از عرض کامل طرح و بالاتر استفاده می کند sm:
در هر ردیف 2 تصویر وجود دارد. lg:
3 تصویر در هر ردیف ، بنابراین استفاده از تصویر بزرگتر در صفحه های کوچکتر منطقی است.
در حالی که پیکربندی تصاویر پاسخگو توصیه می شود پیش نمایش آنچه در مرورگر ایجاد می شود و اطمینان حاصل می کند که نتیجه آن انتظار را برآورده می کند ، ما در تمام وضوح تصاویر تیز داریم و فایل های تصویری خیلی بزرگ نیست.
https://www.youtube.com/watch؟v=gr7r9sh1rss
Blur Preloader ، انتقال CSS
تصویر بزرگ Lightbox به تنهایی Photoswipe را اداره می کند ، ما فعلاً با آن تداخل نخواهیم داشت. اما ما می توانیم تأثیر خوبی در تصاویر تصویر کوچک در پیمایش بی نهایت داشته باشیم. آنها در حال حاضر به اندازه کافی کوچک هستند که سریع بارگیری می کنند ، بنابراین نیازی به استفاده از تصویر با وضوح کوچکتر برای مبهم از قبل نیست ، می توانیم با یک انتقال ساده CSS به همان تأثیر برسیم.
کد زیر آن را انجام می دهد که SRC/Components/React/Gallery.tsx#L132
// src/components/react/Gallery.tsx
const [loadedImages, setLoadedImages] = useState<GalleryImage[]>([]);
const isLoadingPageImages = useMemo(
() => !Object.values(loadedStates).every(Boolean),
[loadedStates, loadedImages.length]
);
useEffect(() => {
const callback: IntersectionObserverCallback = (entries) => {
// must wait here for images to load
if (!isEnd && !isLoadingPageImages && entries[0].isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
};
// ...
// page dependency is important for initial load to work for all resolutions
}, [observerTarget, page, isEnd, isLoadingPageImages]);
const handleLoad = (src: string) => {
setLoadedStates((prev) => ({ ...prev, [src]: true }));
};
{loadedImages.map((image) => (
// ...
<img
{...image.thumbnail}
onLoad={() => handleLoad(image.thumbnail.src)}
alt={loadedStates[image.thumbnail.src] ? 'Gallery image' : ''}
className={cn(
'w-full transition-all duration-[2s] ease-in-out',
loadedStates[image.thumbnail.src]
? 'opacity-100 blur-0 grayscale-0'
: 'opacity-75 blur-sm grayscale'
)}
/>
))}
توجه داشته باشید که ما map()
در اینجا تماس بگیرید و ما برای مجموعه ای از تصاویر حالت های بارگیری را ذخیره می کنیم. این امر به این دلیل است که ما می خواهیم یک انتقال صاف برای کل صفحه جدید از تصاویر داشته باشیم ، نه برای هر تصویر به طور جداگانه زیرا آنها به طور تصادفی بارگیری می شوند و این کمتر زیبایی است. بخش مهم این است isLoadingPageImages
متغیر ، از آن برای مسدود کردن بارگذاری یک صفحه جدید استفاده می شود تا اینکه تمام تصاویر از صفحه قبلی بارگیری شوند. این اتفاق در شرایط پاسخ به ناظر رخ می دهد if (!isEnd && !isLoadingPageImages && entries[0].isIntersecting)
بشر
بخش دیگر انتقال CSS است ، duration-[...]
باید انتخاب شود تا بیشتر از زمان بارگیری تصویر تصویر کوچک واقعی باشد. برای اثر انتقال ، می توانید با کلاسهای Opacity و Filter Tailwind بازی کنید و ببینید چه چیزی برای شما بهترین به نظر می رسد.
کتیبه بی نهایت
ما می خواهیم صفحه بندی را از طریق پیمایش نامحدود مانند EG اینستاگرام پیاده سازی کنیم. بدیهی است ، برای این کار ، گالری باید یک مؤلفه مشتری باشد و ما از CompiresectionObserver برای شناسایی قسمت پایین گالری استفاده خواهیم کرد و باعث بارگذاری صفحه جدیدی از تصاویر خواهیم شد. برای ناظر ما می توانیم از قلاب های آماده از کتابخانه های ابزار مانند UIDOTDEV/USEHOOKS یا streamich/React- استفاده استفاده کنیم اما اجازه می دهیم این بار با اجرای سفارشی خودمان برویم.
کد این در SRC/Components/React/Gallery.tsx#L76 است:
// src/components/react/Gallery.tsx
// sets only page
useEffect(() => {
const callback: IntersectionObserverCallback = (entries) => {
// must wait here for images to load
if (!isEnd && !isLoadingPageImages && entries[0].isIntersecting) {
setPage((prevPage) => prevPage + 1);
}
};
const debouncedCallback = debounce(callback, OBSERVER_DEBOUNCE);
const options: IntersectionObserverInit = { threshold: 1 };
const observer = new IntersectionObserver(debouncedCallback, options);
const observerRef = observerTarget.current;
if (observerRef) observer.observe(observerRef);
return () => {
if (observerRef) observer.unobserve(observerRef);
};
// page dependency is important for initial load to work for all resolutions
}, [observerTarget, page, isEnd, isLoadingPageImages]);
در این کد 3 بخش مهم وجود دارد:
- ما باید شامل شویم
page
متغیر حالت درuseEffect
وابستگی ها آرایه زیرا ما می خواهیم هر بار که صفحه جدید بارهای تصاویر و ارتفاع گالری افزایش می یابد ، اجرای اثر را ایجاد کنیم. همچنین توجه داشته باشید که ما می خوانیمpage
ارزش دولت از استدلال پاسخ به تماس تنظیم کننده دولتsetPage((prevPage) => prevPage + 1)
، به همین دلیل ما نیز باید لیست کنیمpage
درuseEffect
آرایه وابستگی. - ما باید در مورد بارگیری صفحه جدید تصاویر دقیق باشیم. توجه داشته باشید این شرایط
if (!isEnd && !isLoadingPageImages && entries[0].isIntersecting)
این به طور عملی به معنای “بارگذاری صفحه جدید از تصاویر هر زمان 1 است. ما همه تصاویر را بارگذاری نکرده ایم و 2 صفحه قبلی تصاویر کاملاً بارگیری شده است – برای زیبایی شناسی و 3 گالری به پایین – پیش نیاز اصلی است. - ناظر
callback()
محرک ها اغلب ، بنابراین ما باید فرکانس را با استفاده از debouncing محدود کنیم. یادداشتOBSERVER_DEBOUNCE
مقدار ثابت باید از طریق آزمایش و خطای عملی تنظیم و تأیید شود.
یکی دیگر از قسمت های مهم و جالب تشخیص پایین صفحه و نمایش UI لودر:
// src/components/react/Gallery.tsx
{/* control threshold with margin-top */}
{/* must be on top so loader doesn't affect it */}
<div ref={observerTarget} className="mt-0" />
<div
className={cn(
// duration-500 is related to OBSERVER_DEBOUNCE: 300
'flex items-center justify-center transition-all duration-500 ease-in-out',
shouldShowLoader ? 'min-h-48' : 'min-h-0'
)}
>
{shouldShowLoader && <PiSpinnerGapBold className="size-10 sm:size-12 animate-spin mt-4" />}
div>
این می تواند مشکل باشد زیرا آنها به صورت دایره ای وابسته هستند – محرک های تشخیص لودر و نمایش لودر بر موقعیت تشخیص تأثیر می گذارد
div
دارای ارتفاع صفر است و یا در بالا قرار می گیرد یا لودر را زیر آن قرار می دهد. مهم است که لودر فوق باشید زیرا ما به پایین تصاویر علاقه مند هستیم ، نه لودر که به هر حال در چند میلی ثانیه از UI ناپدید می شود.
بخش مهم دیگر کنترل و تنظیم دقیق آستانه عنصر مشاهده شده است
className="mt-0"
، کنترل فرکانس اجرای پاسخ به ناظران با OBSERVER_DEBOUNCE
، تنظیم زمان انتقال برای عنصر لودر duration-500
، مشخص کردن اینکه چند تصویر را بارگیری می کنیم (تعداد ردیف های موجود در گالری) با استفاده از pageSize
ثابت ، و چند صفحه از تصاویر که در ابتدا در صفحه اول بارگیری می کنیم initialPage
ثابت
همه این پارامترها به هم وصل شده اند و باید آنها را برای تجربه پیمایش نامتناهی صاف تنظیم کنید. همچنین توجه داشته باشید که pageSize
وت initialPage
ثابت ها پاسخگو هستند و برای کنترل کامل و ارگونومیک باید برای هر نقطه شکست به طور مستقل تعریف شوند.
می توانید آن را در پرونده ثابت در SRC/Constants/Gallery.ts#L7 مشاهده کنید
// src/constants/gallery.ts
export const GALLERY = {
GALLERY_ID: 'my-gallery',
// Todo: make it responsive
/** step. */
PAGE_SIZE: {
XS: 1,
SM: 2,
LG: 3,
},
/** page dependency in useEffect is more important. To load first screen quickly, set to 3 pages */
INITIAL_PAGE: {
XS: 3,
SM: 3,
LG: 3,
},
/** fine tuned for scroll */
OBSERVER_DEBOUNCE: 300,
} as const;
و نقشه برداری برای ترجمه ثابت به استفاده pageSize
وت initialPage
مقادیر در توابع ابزار در SRC/UTILS/GALLERY.TS#L8 تعریف شده اند:
// src/utils/gallery.ts
const { PAGE_SIZE, INITIAL_PAGE } = GALLERY;
// related to gallery grid css
const breakpointToPageKey = {
XXS: 'XS',
XS: 'XS',
SM: 'SM',
MD: 'SM',
LG: 'LG',
XL: 'LG',
_2XL: 'LG',
} as const;
const defaultPageKey = 'LG' as const;
export const getPageSize = (breakpoint: Breakpoint): number => {
const key = breakpointToPageKey[breakpoint] ?? defaultPageKey;
const pageSize = PAGE_SIZE[key];
return pageSize;
};
export const getInitialPage = (breakpoint: Breakpoint): number => {
const key = breakpointToPageKey[breakpoint] ?? defaultPageKey;
const initialPage = INITIAL_PAGE[key];
return initialPage;
};
با این کار ، ما یک تجربه پیمایش صاف در تمام اندازه های صفحه داریم:
https://www.youtube.com/watch؟v=eeh-aszkur0
همچنین توجه کنید که چگونه ما صفحه جدیدی از تصاویر را برای به روزرسانی "واکشی" می کنیم:
// src/components/react/Gallery.tsx
const fetchImagesUpToPage = (
images: GalleryImage[],
pageSize: number,
nextPage: number
): GalleryImage[] => {
const endIndex = nextPage * pageSize;
const isLastPage = endIndex >= images.length;
// for fetchPageImages pagination startIndex must use loadedImages and not all images and page
const selectedImages = images.slice(0, endIndex);
// load all images for last page
return !isLastPage ? sliceToModN(selectedImages, pageSize) : selectedImages;
};
// converts page to loaded images
useEffect(() => {
const upToPageImages = fetchImagesUpToPage(images, pageSize, page);
setLoadedImages(upToPageImages);
}, [page, images, pageSize]);
در اینجا 2 لحظه مهم وجود دارد:
- از آنجا که ما یک وب سایت استاتیک داریم ، تمام URL های تصویر از قبل در مشتری گنجانده شده و در دسترس هستند ، بنابراین نیازی به محاسبه شاخص شروع نداریم و می توانیم به سادگی از صفر استفاده کنیم
images.slice(0, endIndex);
بشر معمولاً صفحه بندی به معنای تماس های شبکه و پایگاه داده است که به هر دو نیاز دارندstartIndex
وتendIndex
، و اگر ما آن مسیر را طی کردیم ، باید محاسبه کنیمstartIndex
با یافتن آخرین عنصرloadedImages
آرایه دولتی درimages
آرایه و تصویب آنها به عنوان استدلال. - از آنجا که
pageSize
ثابت است که پاسخگو است ، می تواند تغییر کند وقتی EG کاربر در پنجره مرورگر تغییر اندازه می دهد ، بنابراین ما تماس می گیریمsliceToModN(selectedImages, pageSize)
برای ردیف جدید به طور مساوی توجه داشته باشید که ما این را برای صفحه آخر فراخوانی نمی کنیم زیرا ، در نهایت ، ما می خواهیم همه تصاویر را بارگیری کنیم و صحیح استloadedImages
طول آرایه برای محاسبه مهم استisEnd
متغیر
تغییر چیدمان تجمعی
تغییر چیدمان پارامتر مهم وب ویتامین است و بهینه سازی در اینجا چالش برانگیزتر است زیرا ما با یک مؤلفه های مشتری پویا سر و کار داریم. در مؤلفه گالری ما این کار را با تنظیم انجام می دهیم initialPage
ثابت برای بارگیری تصاویر به اندازه کافی برای پر کردن صفحه نمایش گالری اولیه.
// src/constants/gallery.ts
export const GALLERY = {
// ...
PAGE_SIZE: {
XS: 1,
SM: 2,
LG: 3,
},
INITIAL_PAGE: {
XS: 3,
SM: 3,
LG: 3,
},
// ...
} as const;
بهینه سازی دیگری که می توانیم انجام دهیم این است که عنصر ظرف گالری خالی را با آن بکشید flex grow
بشر برای این کار ما باید طرح صفحه را اصلاح کنیم و کلاسهای Tailwind مورد نیاز را از طریق Frontmatter MDX عبور دهیم و articleClass
prop
می توانید آن را در SRC/Layouts/Page.Astro#L38 مشاهده کنید:
// src/layouts/Page.astro
---
import Centered from '@/layouts/Centered.astro';
import { getOpenGraphImagePath } from '@/libs/api/open-graph/image-path';
import { cn } from '@/utils/styles';
export interface Content {
// ...
class?: string;
/** for flex flex-grow min-height to prevent layout shift for client components */
articleClass?: string;
}
// ...
const { title, description, class: className, articleClass } = content;
// ...
---
<Centered {metadata} class={cn(className)}>
{/* in general must not have flex, it will disable margin collapsing in MDX */}
<article class={cn('my-prose', articleClass)}>
<slot />
article>
Centered>
کلاس Flex از Frontmatter MDX در SRC/Pages/Gallery.mdx#L7 عبور می کند:
# src/pages/gallery.mdx
---
layout: '../layouts/Page.astro'
...
class: 'max-w-5xl'
articleClass: 'grow flex flex-col'
---
import Gallery from '../components/Gallery.astro';
# Gallery
class="not-prose grow" />
این باعث می شود تغییر اندازه عناصر DOM کاهش یابد ، آن را در صفحه کاملاً استاتیک کامل نمی کند اما برای استفاده ما به اندازه کافی خوب است.
نکته دیگری که باید بیان شود این است flex
کانتینر فروپاشی حاشیه ای را که برای فاصله های عمودی مناسب در MDX تولید شده HTML مهم است ، غیرفعال می کند. بنابراین اگر این کار را انجام دهید ، باید یک اضافی اضافه کنید
Lighthouse score, old gallery:
Lighthouse score, new gallery:
لطفاً نمره "دسترسی" را در بالا نادیده بگیرید ، زیرا ویژگی های دسترسی هنوز در کل وب سایت حل نشده است.
باکس با PhotoSwipe
برای پیش نمایش تصاویر در Lightbox Full Screen ، ما از کتابخانه آماده Photoswipe استفاده خواهیم کرد که به نظر می رسد جامد ، قابل اعتماد و انعطاف پذیر است. ما از یک نمونه اصلی React از مستندات استفاده خواهیم کرد.
این کد SRC/CONTENTS/React/Gallery.tsx#l98 است
// src/components/react/Gallery.tsx
// lightbox
useEffect(() => {
let lightbox: PhotoSwipeLightbox | null = new PhotoSwipeLightbox({
gallery: '#' + GALLERY_ID,
children: 'a',
pswpModule: () => import('photoswipe'),
});
lightbox.init();
return () => {
lightbox?.destroy();
lightbox = null;
};
}, []);
return (
<>
<div
id={GALLERY_ID}
className="pswp-gallery grid grid-cols-1 gap-1 sm:grid-cols-2 lg:grid-cols-3"
>
{loadedImages.map((image) => (
<a
key={`${GALLERY_ID}--${image.lightbox.src}`}
// lightbox doesn't support responsive image
href={image.lightbox.src}
data-pswp-width={image.lightbox.width}
data-pswp-height={image.lightbox.height}
target="_blank"
rel="noreferrer"
>
<img
{...image.thumbnail}
// ...
/>
a>
))}
div>
{/* ... */}
<>
توجه داشته باشید که به خاطر سادگی ، ما از یک تصویر ثابت ساده استفاده می کنیم و Photoswipe به تنهایی انتقال مقیاس را انجام می دهد. به طور پیش فرض از یک لینک ساده استفاده می کند برای بارگیری
در صفحه کامل Lightbox.
این یک مبادله برای سادگی است. بارگیری یک تصویر پاسخگو با srcset
نیاز به ادغام یک مؤلفه سفارشی دارد که می تواند موضوعی برای مقاله دیگری باشد. یکی دیگر از پیشرفت های احتمالی فعال کردن Closing Lightbox در پس زمینه کلیک بر روی موبایل است که با پیکربندی پیش فرض اینگونه نیست.
اندازه تصویر Lightbox در SRC/LIBS/GALLERY/TRANSFORM.TS#L24 تعریف شده است
// src/libs/gallery/transform.ts
export const lightboxImageOptions = {
...IMAGE_SIZES.FIXED.MDX_2XL_16_9,
};
// src/constants/image.ts
export const IMAGE_SIZES = {
FIXED: {
// ...
MDX_2XL_16_9: { width: TW_SCREENS._2XL, height: TW_SCREENS.HEIGHTS._2XL },
},
// ...
};
کد و نسخه ی نمایشی تکمیل شده
پرونده های مربوطه:
# new gallery https://github.com/nemanjam/nemanjam.github.io/tree/c1e105847d8e7b4ab4aaffad3078726c37f67528
git checkout c1e105847d8e7b4ab4aaffad3078726c37f67528
src/pages/gallery.mdx
src/components/Gallery.astro
src/components/react/Gallery.tsx
src/libs/gallery/images.ts
src/libs/gallery/transform.ts
src/utils/gallery.ts
src/constants/gallery.ts
src/constants/image.ts
src/components/react/hooks/useScrollDown.tsx
src/components/react/hooks/useWidth.tsx
# old gallery https://github.com/nemanjam/nemanjam.github.io/tree/e0165b295db2ccc72bbbb7be4bdd7eb48f7dedae
git checkout e0165b295db2ccc72bbbb7be4bdd7eb48f7dedae
دیگر
این یک خواندن بسیار طولانی بود ، از توجه و فداکاری شما متشکرم. آیا خودتان یک گالری تصویر Astro را پیاده سازی کرده اید و از یک رویکرد متفاوت استفاده کرده اید؟ آیا پیشنهادهایی برای پیشرفت دارید یا چیز نادرستی را مشاهده می کنید؟ دریغ نکنید که نظر خود را در زیر بگذارید.
منابع