126 lines
4.6 KiB
PHP
126 lines
4.6 KiB
PHP
<?php
|
||||
|
|
/**
|
|||
|
|
* 照片灯箱(Lightbox):点页面里带 class="js-photo" 的链接,就在当前页弹窗里看大图,
|
|||
|
|
* 左右箭头(或键盘 ←/→、触摸滑动)切上一张/下一张,不再新开窗口。
|
|||
|
|
*
|
|||
|
|
* 用法:
|
|||
|
|
* 1. 图片链接写成 <a class="js-photo" href="<大图地址>" data-cap="<说明文字>">…</a>
|
|||
|
|
* (没有 data-cap 时回落到 title / alt / href)
|
|||
|
|
* 2. 在页面末尾 require 本模板(一份就够,多页共用同一份标记与脚本)
|
|||
|
|
*
|
|||
|
|
* 依赖外部变量:无。
|
|||
|
|
*/
|
|||
|
|
?>
|
|||
|
|
<div class="lightbox" id="lightbox" aria-hidden="true" role="dialog" aria-label="图片浏览">
|
|||
|
|
<div class="lightbox-backdrop" data-lb-close="1"></div>
|
|||
|
|
|
|||
|
|
<button type="button" class="lightbox-nav lightbox-prev" data-lb-prev="1" aria-label="上一张">‹</button>
|
|||
|
|
|
|||
|
|
<figure class="lightbox-body">
|
|||
|
|
<img class="lightbox-img" id="lightbox-img" alt="">
|
|||
|
|
<figcaption class="lightbox-cap" id="lightbox-cap"></figcaption>
|
|||
|
|
</figure>
|
|||
|
|
|
|||
|
|
<button type="button" class="lightbox-nav lightbox-next" data-lb-next="1" aria-label="下一张">›</button>
|
|||
|
|
|
|||
|
|
<button type="button" class="lightbox-close" data-lb-close="1" aria-label="关闭">×</button>
|
|||
|
|
<div class="lightbox-count" id="lightbox-count"></div>
|
|||
|
|
</div>
|
|||
|
|
|
|||
|
|
<script>
|
|||
|
|
(function () {
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
var box = document.getElementById('lightbox');
|
|||
|
|
if (!box) return;
|
|||
|
|
var img = document.getElementById('lightbox-img');
|
|||
|
|
var cap = document.getElementById('lightbox-cap');
|
|||
|
|
var count = document.getElementById('lightbox-count');
|
|||
|
|
|
|||
|
|
var items = []; // 打开时现收集页面上所有 js-photo(DOM 顺序)
|
|||
|
|
var idx = 0;
|
|||
|
|
|
|||
|
|
function collect() {
|
|||
|
|
return Array.prototype.slice.call(document.querySelectorAll('a.js-photo'));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function captionOf(a) {
|
|||
|
|
return a.getAttribute('data-cap') || a.getAttribute('title')
|
|||
|
|
|| (a.querySelector('img') ? a.querySelector('img').alt : '') || '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function show(i) {
|
|||
|
|
if (!items.length) return;
|
|||
|
|
idx = (i + items.length) % items.length; // 循环切换
|
|||
|
|
var a = items[idx];
|
|||
|
|
var full = a.getAttribute('data-full') || a.getAttribute('href');
|
|||
|
|
img.alt = captionOf(a);
|
|||
|
|
cap.textContent = captionOf(a);
|
|||
|
|
count.textContent = items.length > 1 ? (idx + 1) + ' / ' + items.length : '';
|
|||
|
|
img.src = full;
|
|||
|
|
// 预加载相邻两张,切换不等待
|
|||
|
|
[idx - 1, idx + 1].forEach(function (j) {
|
|||
|
|
var na = items[(j + items.length) % items.length];
|
|||
|
|
if (!na) return;
|
|||
|
|
var pre = new Image();
|
|||
|
|
pre.src = na.getAttribute('data-full') || na.getAttribute('href');
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function openAt(anchor) {
|
|||
|
|
items = collect();
|
|||
|
|
var at = items.indexOf(anchor);
|
|||
|
|
if (at < 0) { items = [anchor]; at = 0; }
|
|||
|
|
box.classList.add('open');
|
|||
|
|
box.setAttribute('aria-hidden', 'false');
|
|||
|
|
document.body.style.overflow = 'hidden';
|
|||
|
|
show(at);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function close() {
|
|||
|
|
box.classList.remove('open');
|
|||
|
|
box.setAttribute('aria-hidden', 'true');
|
|||
|
|
document.body.style.overflow = '';
|
|||
|
|
img.src = '';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
document.addEventListener('click', function (ev) {
|
|||
|
|
var link = ev.target.closest ? ev.target.closest('a.js-photo') : null;
|
|||
|
|
if (link) {
|
|||
|
|
ev.preventDefault();
|
|||
|
|
openAt(link);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
if (!box.classList.contains('open')) return;
|
|||
|
|
if (ev.target.closest('[data-lb-prev]')) { show(idx - 1); return; }
|
|||
|
|
if (ev.target.closest('[data-lb-next]')) { show(idx + 1); return; }
|
|||
|
|
if (ev.target.closest('[data-lb-close]')) { close(); }
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
document.addEventListener('keydown', function (ev) {
|
|||
|
|
if (!box.classList.contains('open')) return;
|
|||
|
|
if (ev.key === 'Escape') close();
|
|||
|
|
else if (ev.key === 'ArrowLeft') show(idx - 1);
|
|||
|
|
else if (ev.key === 'ArrowRight') show(idx + 1);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// 触摸滑动切图(手机上不用去够箭头)
|
|||
|
|
var touchX = null, touchY = null;
|
|||
|
|
box.addEventListener('touchstart', function (ev) {
|
|||
|
|
if (ev.touches.length === 1) {
|
|||
|
|
touchX = ev.touches[0].clientX;
|
|||
|
|
touchY = ev.touches[0].clientY;
|
|||
|
|
}
|
|||
|
|
}, { passive: true });
|
|||
|
|
box.addEventListener('touchend', function (ev) {
|
|||
|
|
if (touchX === null || !ev.changedTouches.length) return;
|
|||
|
|
var dx = ev.changedTouches[0].clientX - touchX;
|
|||
|
|
var dy = ev.changedTouches[0].clientY - touchY;
|
|||
|
|
if (Math.abs(dx) > 48 && Math.abs(dx) > Math.abs(dy) * 1.5) {
|
|||
|
|
show(dx < 0 ? idx + 1 : idx - 1);
|
|||
|
|
}
|
|||
|
|
touchX = touchY = null;
|
|||
|
|
}, { passive: true });
|
|||
|
|
})();
|
|||
|
|
</script>
|