Looking to hire Laravel developers? Try LaraJobs

laravel-islands-gallery maintained by aaix

Author
Last update
2026/11/02 17:57 (v1.0.3)
License
Links
Downloads
0

Comments
comments powered by Disqus

Two Vue components share one image list. Gallery sits in the page; Lightbox takes over the screen. The gallery brings its own lightbox, and the lightbox also works alone, opened from anything you like.

  • Inline carousel — a 3∶2 stage with arrows, swipe, counter and a thumbnail strip
  • Lightbox — zoom at the pointer, pan, pinch, swipe, slideshow and fullscreen
  • Opens out of the picture — the image grows from the thumbnail that was clicked and closes back into it
  • Keyboard and touch — arrows, Esc and Space; swipe to step, drag down to close
  • Your wording — every label is a prop, English fallbacks only
<script setup>
import { Gallery } from '@aaix/laravel-islands-gallery/vue';
</script>

<template>
    <Gallery :images="[{ src: '/photos/1.jpg', thumb: '/photos/1-small.jpg', caption: 'Front' }]" />
</template>

Installation

composer require aaix/laravel-islands-gallery

Add the Vite plugin — it registers the import name @aaix/laravel-islands-gallery and, when the package is installed from a Composer path repository, points it at the working copy:

// vite.config.js
import islandsGallery from './vendor/aaix/laravel-islands-gallery/vite.js';

export default defineConfig({
    plugins: [/* … */ islandsGallery()],
});

Let Tailwind see the package's classes, in every stylesheet that renders islands:

@source '../../vendor/aaix/laravel-islands-gallery/resources/js/**/*';

Images

Every image is a plain object; only src is required.

{ src: 'https://…/full.jpg', thumb: 'https://…/336.jpg', caption: 'Front view', alt: 'Die cutter, front' }
Key
src The full-size image, also what the download saves
thumb A small version for the thumbnail strip; falls back to src
caption Shown on the stage and in the lightbox
alt Alternative text; falls back to the caption
filename Name of the saved file; falls back to the last part of the URL
downloads Sizes to choose from, see Download

Map whatever your server sends into this shape in a computed — the components read no other keys.

Gallery

<Gallery :images="images" :labels="labels" @change="(index) => …" />
Prop Default
images — The list above; renders nothing when empty
start 0 Index shown first
aspect '3 / 2' Stage ratio
fit 'cover' 'contain' when nothing may be cropped — documents, drawings
loop true Wrap from the last image to the first
thumbnails true Strip below the stage
lightbox true A click or tap on the stage opens the full-screen view
captions true The current image's caption, bottom centre on the stage
downloadable true Download button in the lightbox
labels English See Wording

Emits change(index) whenever another image comes to the front. The arrows appear on hover and stay hidden on touch devices, where the stage swipes.

Lightbox

Use Lightbox alone when the trigger is something else — a table thumbnail, a button:

<Lightbox
    v-model:index="openIndex"
    :images="images"
    :labels="labels"
    :origin="(index) => thumbs[index]?.getBoundingClientRect()"
/>

index is null while closed. Set it to open; the lightbox writes it back on every step and emits close.

Prop Default
images — The list above
index null The open image, null while closed — use v-model:index
origin null (index) => DOMRect of the picture on screen; the image zooms out of it and back into it
originRadius 12 Corner radius of that picture in px, so the shape matches on the way back
loop true Wrap from the last image to the first
thumbnails true Strip along the bottom
slideshowInterval 4000 Milliseconds per image in the slideshow
maxZoom 4 Largest zoom factor
downloadable true Download button in the toolbar
labels English See Wording
Input Effect
← →, swipe Previous / next image
Click, double tap Zoom in at the pointer, again to zoom out
Wheel, pinch Zoom continuously
Drag Pan a zoomed image
Esc Zoom out first, then close
Drag down (touch) Close
Tap Hide or show the controls
Space Start or pause the slideshow

The toolbar carries the counter, zoom, slideshow, fullscreen, download and close. The lightbox sits on the modal layer (z-index 70) and is rendered into body, so it also opens from inside a modal.

Open from anywhere

openLightbox opens one shared lightbox without placing a component — for a photo in a table cell, a card or a list:

import { openLightbox } from '@aaix/laravel-islands-gallery/vue';

openLightbox(images, {
    start: index,
    origin: (position) => thumbs[position]?.getBoundingClientRect() ?? null,
    labels,
});

start is the image shown first; every other option is a Lightbox prop. The lightbox mounts on first use and is reused after that.

Download

The download button saves src under filename. To offer several sizes, give the image a downloads list — the button then opens a menu with one entry per size:

{
    src: 'https://…/1500.jpg',
    filename: 'die-cutter-front.jpg',
    downloads: [
        { label: t('Original'), src: 'https://…/original.jpg', filename: 'die-cutter-front.jpg' },
        { label: t('Web (1500 px)'), src: 'https://…/1500.jpg', filename: 'die-cutter-front-1500.jpg' },
    ],
}

An entry without filename takes the image's. Name files after the record, not the position, so two downloads never collide.

The file is fetched and saved in place. When the server refuses that — another domain without CORS headers — the browser opens the image in a new tab instead, where it can still be saved by hand.

Wording

The package ships English fallbacks only. Pass your own strings through your translation layer:

const labels = computed(() => ({
    dialog: t('Image viewer'),
    counter: t(':current / :total'),
    image: t('Image :number'),
    open: t('Open image'),
    previous: t('Previous image'),
    next: t('Next image'),
    close: t('Close'),
    zoomIn: t('Zoom in'),
    zoomOut: t('Zoom out'),
    play: t('Start slideshow'),
    pause: t('Pause slideshow'),
    fullscreen: t('Full screen'),
    exitFullscreen: t('Exit full screen'),
    download: t('Download'),
}));

:current, :total and :number are filled in by the package. Keys you leave out keep their English fallback.

Demo

demo/ is a standalone page with the gallery and sample photos, no Laravel app needed. Every push to main publishes it as the live demo. To run it locally:

cd demo
npm install
npm run dev

The browser tests run against the same page and on every push:

npx playwright install chromium
npm test

License

MIT

Sample photos in the screenshots: Unsplash.