Zoom image from origin
Zoom images from their origin while opening the image gallery.
You need to know the original image size upfront and provide it via data-lg-size attribute as data-lg-size=“1920-1280”
(lgSize: '1920-1280' on an item object). Image optimizers, build-time image plugins and CMS APIs all expose the natural width and height.
If you don’t know the size of a few images in the list, you can skip the data-lg-size attribute for the particular slides; lightGallery will show the default animation if data-lg-size is not available.
On close, the slide flies back to its thumbnail. A slide with no thumbnail to return to, because its trigger is hidden or collapsed
(the overflow photos behind a “+9 photos” tile in a collage) or because it has no data-lg-size, shrinks to the centre of the
screen and fades out instead. The same happens when zoomFromOrigin is off or the gallery is dynamic.
Demo
HTML
<div id="gallery-zoom-from-origin-demo">
<a
data-lg-size="1600-1067"
href="img/img1.jpg"
>
<img src="img/thumb1.jpg" />
</a>
<a
data-lg-size="1600-1067"
href="img/img2.jpg"
>
<img src="img/thumb2.jpg" />
</a>
<!-- more items -->
</div>JavaScript
import lightGallery from 'lightgallery';
lightGallery(document.getElementById('gallery-zoom-from-origin-demo'), {
// on by default, the lightbox grows out of the clicked thumbnail
zoomFromOrigin: true,
});import { LightGallery, LightGalleryItem } from '@lightgallery/react';
const items = [
{
src: 'img/img1.jpg',
thumb: 'img/thumb1.jpg',
alt: 'Mountains',
// "WIDTH-HEIGHT" of the full-size image: required for the flight
lgSize: '1600-1067',
},
{
src: 'img/img2.jpg',
thumb: 'img/thumb2.jpg',
alt: 'Forest path',
lgSize: '1600-2400',
},
];
// On by default: the slide grows out of the clicked thumbnail. The
// trigger element is measured automatically; in controlled mode pass
// originRect instead.
<LightGallery zoomFromOrigin>
{items.map((item) => (
<LightGalleryItem key={item.src} item={item}>
<img src={item.thumb} alt={item.alt} />
</LightGalleryItem>
))}
</LightGallery>;<script setup>
import { LightGallery, LgItem } from '@lightgallery/vue';
const items = [
{
src: 'img/img1.jpg',
thumb: 'img/thumb1.jpg',
alt: 'Mountains',
// "WIDTH-HEIGHT" of the full-size image: required for the flight
lgSize: '1600-1067',
},
{
src: 'img/img2.jpg',
thumb: 'img/thumb2.jpg',
alt: 'Forest path',
lgSize: '1600-2400',
},
];
</script>
<template>
<!-- On by default: the slide grows out of the clicked thumbnail. -->
<LightGallery :zoom-from-origin="true">
<LgItem v-for="item of items" :key="item.src" :item="item">
<img :src="item.thumb" :alt="item.alt" />
</LgItem>
</LightGallery>
</template>import { Component } from '@angular/core';
import {
LgGalleryComponent,
LgGalleryItemDirective,
type LgGalleryItem,
} from '@lightgallery/angular';
@Component({
selector: 'app-gallery',
imports: [LgGalleryComponent, LgGalleryItemDirective],
template: `
<!-- On by default: the slide grows out of the clicked thumbnail. -->
<lg-gallery [zoomFromOrigin]="true">
@for (item of items; track item.src) {
<a [lgGalleryItem]="item">
<img [src]="item.thumb" [alt]="item.alt" />
</a>
}
</lg-gallery>
`,
})
export class Gallery {
items: LgGalleryItem[] = [
{
src: 'img/img1.jpg',
thumb: 'img/thumb1.jpg',
alt: 'Mountains',
// "WIDTH-HEIGHT" of the full-size image: required for the flight
lgSize: '1600-1067',
},
{
src: 'img/img2.jpg',
thumb: 'img/thumb2.jpg',
alt: 'Forest path',
lgSize: '1600-2400',
},
];
}Cropped thumbnails
A grid tile that shows only part of the photo, an img with object-fit: cover or an element with background-size: cover,
flies as a whole by default, squashed into its box. The origin crop plugin flies it from its crop instead: the part the tile
shows grows at a uniform scale and the rest of the photo is revealed around it, following object-position or
background-position. Nothing is needed in the markup or the stylesheets; the plugin reads the thumbnail’s computed fit, and
thumbnails the built-in flight already lands exactly keep it. A thumbnail file that is itself a crop of the photo is taken as
a centred crop.
The tiles below are square and crop portrait and landscape photos alike, each from a different object-position. Open one,
then untick the plugin and open it again to compare with the default flight.
HTML
<div id="gallery-cropped-thumbnails" class="tiles">
<a data-lg-size="1600-1067" href="img/img1.jpg">
<img src="img/thumb1.jpg" style="object-position: 0% 50%" />
</a>
<a data-lg-size="1067-1600" href="img/img2.jpg">
<img src="img/thumb2.jpg" style="object-position: 50% 0%" />
</a>
<!-- more items -->
</div>CSS
.tiles img {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: cover;
}JavaScript
import lightGallery from 'lightgallery';
import lgOriginCrop from 'lightgallery/plugins/originCrop';
lightGallery(document.getElementById('gallery-cropped-thumbnails'), {
plugins: [lgOriginCrop],
});import { LightGallery, LightGalleryItem } from '@lightgallery/react';
import OriginCrop from '@lightgallery/react/plugins/originCrop';
const items = [
{
src: 'img/img1.jpg',
thumb: 'img/thumb1.jpg',
alt: 'Mountains',
// "WIDTH-HEIGHT" of the full-size image: required for the flight
lgSize: '1600-1067',
// the part of the photo the tile shows
position: '0% 50%',
},
{
src: 'img/img2.jpg',
thumb: 'img/thumb2.jpg',
alt: 'Forest path',
lgSize: '1067-1600',
position: '50% 0%',
},
];
// `.tiles img` is square and crops with `object-fit: cover`.
<LightGallery plugins={[OriginCrop]}>
<div className="tiles">
{items.map((item) => (
<LightGalleryItem key={item.src} item={item}>
<img
src={item.thumb}
alt={item.alt}
style={{ objectPosition: item.position }}
/>
</LightGalleryItem>
))}
</div>
</LightGallery>;<script setup>
import { LightGallery, LgItem } from '@lightgallery/vue';
import OriginCrop from '@lightgallery/vue/plugins/originCrop';
const plugins = [OriginCrop];
const items = [
{
src: 'img/img1.jpg',
thumb: 'img/thumb1.jpg',
alt: 'Mountains',
// "WIDTH-HEIGHT" of the full-size image: required for the flight
lgSize: '1600-1067',
// the part of the photo the tile shows
position: '0% 50%',
},
{
src: 'img/img2.jpg',
thumb: 'img/thumb2.jpg',
alt: 'Forest path',
lgSize: '1067-1600',
position: '50% 0%',
},
];
</script>
<template>
<!-- `.tiles img` is square and crops with `object-fit: cover`. -->
<LightGallery :plugins="plugins">
<div class="tiles">
<LgItem v-for="item of items" :key="item.src" :item="item">
<img
:src="item.thumb"
:alt="item.alt"
:style="{ objectPosition: item.position }"
/>
</LgItem>
</div>
</LightGallery>
</template>import { Component } from '@angular/core';
import {
LgGalleryComponent,
LgGalleryItemDirective,
type LgGalleryItem,
} from '@lightgallery/angular';
import { withOriginCrop } from '@lightgallery/angular/plugins/originCrop';
@Component({
selector: 'app-gallery',
imports: [LgGalleryComponent, LgGalleryItemDirective],
template: `
<!-- .tiles img is square and crops with object-fit: cover. -->
<lg-gallery [features]="features">
<div class="tiles">
@for (item of items; track item.src) {
<a [lgGalleryItem]="item">
<img
[src]="item.thumb"
[alt]="item.alt"
[style.object-position]="item.position"
/>
</a>
}
</div>
</lg-gallery>
`,
})
export class Gallery {
features = [withOriginCrop()];
items = [
{
src: 'img/img1.jpg',
thumb: 'img/thumb1.jpg',
alt: 'Mountains',
// "WIDTH-HEIGHT" of the full-size image: required for the flight
lgSize: '1600-1067',
// the part of the photo the tile shows
position: '0% 50%',
},
{
src: 'img/img2.jpg',
thumb: 'img/thumb2.jpg',
alt: 'Forest path',
lgSize: '1067-1600',
position: '50% 0%',
},
] satisfies (LgGalleryItem & { position: string })[];
}
lightGallery