
Elementor Pro provides several gallery options out of the box, but I ran into a few consistent issues with all of them. First, the thumbnail images were loaded via JavaScript rather than standard <img> tags, which caused visible delays before they appeared, especially on slower connections. Second, Elementor’s default lightbox relies on the eicons font file for its navigation and close icons. Since I prefer to disable all icon fonts on the frontend for performance reasons, this dependency broke the lightbox controls.
To solve both problems, I created my own Elementor Simple Gallery plugin, a lightweight custom Elementor widget that loads images using plain HTML <img> elements, uses standard Unicode characters for the lightbox controls instead of icon fonts, and relies on minimal, efficient JavaScript solely to handle the lightbox’s open, close, and navigation behavior. The result is a gallery that loads instantly, respects native browser image handling (including lazy loading and responsive srcset attributes), and maintains a clean, responsive lightbox experience without any unnecessary scripts or external dependencies.
Place this PHP into a folder/file named elementor-simple-gallery/elementor-simple-gallery.php
start_controls_section( 'content_section', [
'label' => __( 'Gallery', 'elementor' ),
'tab' => \Elementor\Controls_Manager::TAB_CONTENT,
]);
$this->add_control( 'gallery', [
'label' => __( 'Add Images', 'elementor' ),
'type' => \Elementor\Controls_Manager::GALLERY,
]);
$this->add_responsive_control( 'columns', [
'label' => __( 'Columns', 'elementor' ),
'type' => \Elementor\Controls_Manager::NUMBER,
'min' => 1,
'max' => 6,
'default' => 2,
]);
$this->add_control( 'gap', [
'label' => __( 'Gap (px)', 'elementor' ),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => 10,
]);
$this->add_control( 'border_radius', [
'label' => __( 'Border Radius (px)', 'elementor' ),
'type' => \Elementor\Controls_Manager::NUMBER,
'default' => 0,
]);
$this->add_control( 'aspect_ratio', [
'label' => __( 'Aspect Ratio', 'elementor' ),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => '1/1',
'options' => [
'1/1' => 'Square 1:1',
'4/3' => 'Landscape 4:3',
'3/2' => 'Landscape 3:2',
'16/9' => 'Widescreen 16:9',
'2/3' => 'Portrait 2:3',
'3/4' => 'Portrait 3:4',
'9/16' => 'Tall 9:16',
'auto' => 'Auto (use original)',
],
]);
$image_sizes = get_intermediate_image_sizes();
$options = array_combine( $image_sizes, $image_sizes );
$options['full'] = __( 'Full Size', 'elementor' );
$this->add_control( 'thumb_size', [
'label' => __( 'Thumbnail Size', 'elementor' ),
'type' => \Elementor\Controls_Manager::SELECT,
'default' => 'medium',
'options' => $options,
]);
$this->end_controls_section();
}
public function render_plain_content() {
$this->render();
}
protected function render() {
$s = $this->get_settings_for_display();
$imgs = $s['gallery'];
if ( empty( $imgs ) ) return;
$cols = max( 1, (int) $s['columns'] );
$gap = (int) $s['gap'];
$radius = (int) $s['border_radius'];
$ratio = esc_attr( $s['aspect_ratio'] );
$thumb_size = ! empty( $s['thumb_size'] ) ? $s['thumb_size'] : 'medium';
$cols_desktop = max( 1, (int) ( $s['columns'] ?? 2 ) );
$cols_tablet = max( 1, (int) ( $s['columns_tablet'] ?? 2 ) );
$cols_mobile = max( 1, (int) ( $s['columns_mobile'] ?? 1 ) );
$container_factor_desktop = apply_filters( 'ta_simple_gallery_container_factor_desktop', 0.5 );
$container_factor_tablet = apply_filters( 'ta_simple_gallery_container_factor_tablet', 0.8 );
$container_factor_mobile = apply_filters( 'ta_simple_gallery_container_factor_mobile', 1.0 );
$vw_desktop = round( (100 * $container_factor_desktop) / $cols_desktop, 2 );
$vw_tablet = round( (100 * $container_factor_tablet) / $cols_tablet, 2 );
$vw_mobile = round( (100 * $container_factor_mobile) / $cols_mobile, 2 );
$sizes_attr = sprintf(
'(max-width: 600px) %svw, (max-width: 1024px) %svw, %svw',
$vw_mobile,
$vw_tablet,
$vw_desktop
);
echo '';
$i = 0;
foreach ( $imgs as $img ) {
$i++;
$id = $img['id'];
$full = wp_get_attachment_image_url( $id, 'full' );
$alt = esc_attr( get_post_meta( $id, '_wp_attachment_image_alt', true ) );
$src = wp_get_attachment_image_url( $id, $thumb_size );
$srcset = wp_get_attachment_image_srcset( $id, $thumb_size );
echo '';
echo '
';
echo ' ';
}
echo '
❮
❯
✕
';
}
}
$widgets_manager->register( new Elementor_Simple_Gallery_Widget() );
});
/*--------------------------------------------------------------
# Conditional Asset Loading
--------------------------------------------------------------*/
add_action( 'elementor/frontend/after_render', function( $widget ) {
if ( $widget->get_name() === 'simple_gallery' ) {
if ( ! defined( 'TA_SIMPLE_GALLERY_LOADED' ) ) {
define( 'TA_SIMPLE_GALLERY_LOADED', true );
ta_simple_gallery_enqueue_assets();
}
}
}, 10, 1 );
add_action( 'elementor/editor/after_enqueue_scripts', 'ta_simple_gallery_enqueue_assets' );
add_action( 'elementor/preview/enqueue_styles', 'ta_simple_gallery_enqueue_assets' );
function ta_simple_gallery_enqueue_assets() {
$plugin_url = plugin_dir_url( __FILE__ );
wp_enqueue_style(
'simple-gallery',
$plugin_url . 'assets/simple-gallery.css',
[],
'2.4.3'
);
wp_enqueue_script(
'simple-gallery',
$plugin_url . 'assets/simple-gallery.js',
[],
'2.4.3',
true
);
}
Place this JavaScript into a folder/file named elementor-simple-gallery/assets/simple-gallery.js
(function () {
const figures = document.querySelectorAll(".simple-gallery figure");
const lightbox = document.getElementById("ta-lightbox");
if (!figures.length || !lightbox) return;
// Ensure lightbox is appended directly to body for correct z-index layering
function moveLightboxToBody() {
if (lightbox.parentNode !== document.body) {
document.body.appendChild(lightbox);
}
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", moveLightboxToBody);
} else {
moveLightboxToBody();
}
let img = lightbox.querySelector(".lightbox-image");
const prev = lightbox.querySelector(".prev");
const next = lightbox.querySelector(".next");
const close = lightbox.querySelector(".close");
let index = 0;
let isLoading = false;
let lastURL = null;
function open(i) {
index = i;
if (!lightbox.classList.contains("active")) {
lightbox.classList.add("active");
document.body.style.overflow = "hidden";
}
swapImage(index);
}
function closeBox() {
lightbox.classList.remove("active");
document.body.style.overflow = "";
isLoading = false;
lastURL = null;
if (img) img.removeAttribute("src");
}
function showNext(dir) {
if (isLoading) return;
index = (index + dir + figures.length) % figures.length;
swapImage(index);
}
// Safari-safe image swap with robust fallbacks
function swapImage(i) {
const f = figures[i];
const full = f.dataset.full;
const alt = f.querySelector("img").alt;
if (isLoading && full === lastURL) return;
isLoading = true;
lastURL = full;
prev.style.pointerEvents = "none";
next.style.pointerEvents = "none";
close.style.pointerEvents = "none";
const newImg = document.createElement("img");
newImg.className = "lightbox-image";
newImg.alt = alt;
newImg.style.opacity = "0";
newImg.decoding = "async";
newImg.loading = "eager";
newImg.setAttribute("fetchpriority", "high");
img.after(newImg);
const preload = new Image();
preload.decoding = "async";
let finalized = false;
function finalizeSuccess() {
if (finalized) return;
finalized = true;
img.remove();
img = newImg;
img.style.opacity = "";
isLoading = false;
prev.style.pointerEvents = "";
next.style.pointerEvents = "";
close.style.pointerEvents = "";
}
function finalizeFailure() {
if (finalized) return;
finalized = true;
const busted = full + (full.includes("?") ? "&" : "?") + "v=" + Date.now();
newImg.src = busted;
newImg.onload = finalizeSuccess;
newImg.onerror = () => {
img.style.opacity = "";
isLoading = false;
};
}
preload.onload = function () {
requestAnimationFrame(() =>
requestAnimationFrame(() => {
newImg.src = full;
if (newImg.complete) finalizeSuccess();
else newImg.onload = finalizeSuccess;
})
);
};
preload.onerror = finalizeFailure;
preload.src = full;
// Fallback if Safari silently drops the request
setTimeout(() => {
if (!finalized && !newImg.src) {
newImg.src = full;
if (newImg.complete) finalizeSuccess();
}
}, 500);
}
// Events
figures.forEach((f, i) => f.addEventListener("click", () => open(i)));
prev.addEventListener("click", () => showNext(-1));
next.addEventListener("click", () => showNext(1));
close.addEventListener("click", closeBox);
lightbox.addEventListener("click", (e) => {
if (e.target === lightbox) closeBox();
});
document.addEventListener("keydown", (e) => {
if (!lightbox.classList.contains("active")) return;
if (e.key === "Escape") closeBox();
if (e.key === "ArrowLeft") showNext(-1);
if (e.key === "ArrowRight") showNext(1);
});
// BFCache / history restore fix
window.addEventListener("pageshow", (e) => {
if (e.persisted) {
const lb = document.getElementById("ta-lightbox");
if (lb) lb.classList.remove("active");
document.body.style.overflow = "";
}
});
})();
Place this CSS into a folder/file named elementor-simple-gallery/assets/simple-gallery.css
.simple-gallery {
display: grid;
grid-template-columns: repeat(var(--cols), 1fr);
gap: var(--gap);
}
.simple-gallery figure {
margin: 0;
overflow: hidden;
border-radius: var(--radius);
}
.simple-gallery figure:not([style*="--ratio:auto"]) {
aspect-ratio: var(--ratio);
}
.simple-gallery img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
cursor: pointer;
transition: transform .3s;
}
/* No hover zoom */
.simple-gallery img:hover { transform: none; }
/* Lightbox */
.lightbox {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100vw;
height: 100vh;
display: none;
align-items: center;
justify-content: center;
z-index: 99999;
box-sizing: border-box;
padding: 2rem;
background: rgba(0,0,0,0.9);
}
.lightbox.active { display: flex; }
.lightbox img.lightbox-image {
display: block;
width: auto;
height: auto;
max-width: 90vw !important;
max-height: 90vh !important;
object-fit: contain !important;
margin: auto;
box-shadow: 0 0 25px rgba(0,0,0,.6);
}
/* Controls */
.lightbox .nav,
.lightbox .close {
position: absolute;
color: #fff;
font-size: 48px;
cursor: pointer;
user-select: none;
opacity: 0.85;
transition: opacity .2s, transform .2s;
z-index: 5;
line-height: 1;
padding: 20px;
/* outline over light images */
-webkit-text-stroke: 0.5px rgba(0,0,0,0.5);
text-shadow: 0.5px 0.5px 1px rgba(0,0,0,0.3), -0.5px -0.5px 1px rgba(0,0,0,0.3);
}
.lightbox .nav:hover,
.lightbox .close:hover {
opacity: 1;
transform: scale(1.1);
}
.lightbox .nav.prev { left: 10px; }
.lightbox .nav.next { right: 10px; }
.lightbox .close { top: 15px; right: 25px; font-size: 40px; }
@media (max-width: 767px) {
.lightbox .nav { font-size: 40px; padding: 30px 20px; }
.lightbox .nav.prev { left: 5px; }
.lightbox .nav.next { right: 5px; }
}