Files
wordpress-theme-aksrt/assets/js/enhancements.js
T
kate522 f929cc649c feat(shell): header multifunc + window._win + float cart/VIP
Multi-type publish/VIP CTA in nav, zibll-like _win runtime bridge for
lightbox flags, and floating shop_cart/pay_vip commerce buttons.
2026-07-18 22:16:17 +08:00

612 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AKSRT Enhancements — 全局 UI 增强
*
* 包含:Toast 通知、图片灯箱、滚动方向感知
*
* @package AKSRT
*/
(function () {
'use strict';
/* ============================================================
* 1. Toast 通知系统
* ============================================================ */
var toastContainer = null;
function ensureToastContainer() {
if (!toastContainer || !document.body.contains(toastContainer)) {
toastContainer = document.createElement('div');
toastContainer.className = 'aksrt-toast-container';
document.body.appendChild(toastContainer);
}
return toastContainer;
}
/**
* 显示 Toast 通知
*
* @param {string} message 消息文本
* @param {string} type 类型: success, info, warning, danger/error, loading
* @param {number} duration 显示时长(ms),0 表示不自动关闭
* @returns {Object} { dismiss: Function, update: Function }
*/
function showToast(message, type, duration) {
type = type || 'success';
if (type === 'error') type = 'danger';
duration = duration !== undefined ? duration : 3500;
if (duration > 0 && duration < 100) duration = duration * 1000;
var container = ensureToastContainer();
var icons = {
success: 'fas fa-check-circle',
info: 'fas fa-bell',
warning: 'fas fa-exclamation-circle',
danger: 'fas fa-times-circle',
loading: 'fas fa-spinner',
};
var el = document.createElement('div');
el.className = 'aksrt-toast aksrt-toast--' + type;
el.innerHTML =
'<span class="aksrt-toast__icon"><i class="' + (icons[type] || icons.info) + '"></i></span>' +
'<span class="aksrt-toast__text">' + message + '</span>';
container.appendChild(el);
var timer = null;
function dismiss() {
if (timer) clearTimeout(timer);
el.classList.add('is-dismissing');
setTimeout(function () {
if (el.parentNode) el.remove();
}, 300);
}
function update(newMessage, newType) {
if (newType) {
el.className = 'aksrt-toast aksrt-toast--' + newType;
var iconEl = el.querySelector('.aksrt-toast__icon i');
if (iconEl) iconEl.className = icons[newType] || icons.info;
}
var textEl = el.querySelector('.aksrt-toast__text');
if (textEl) textEl.textContent = newMessage;
if (newType !== 'loading' && duration > 0) {
if (timer) clearTimeout(timer);
timer = setTimeout(dismiss, duration);
}
}
el.addEventListener('click', dismiss);
if (type !== 'loading' && duration > 0) {
timer = setTimeout(dismiss, duration);
}
return { dismiss: dismiss, update: update };
}
// 暴露到全局
window.AksrtToast = showToast;
/* ============================================================
* 2. 图片灯箱 — zoom / thumbnails / slideshow / fullscreen /
* mousewheel / origin animation / smart download
* ============================================================ */
var lightboxEl = null;
var lightboxImg = null;
var lightboxCaption = null;
var lightboxCounter = null;
var lightboxThumbsWrap = null;
var currentGallery = [];
var currentIndex = 0;
var currentZoom = 1;
var autoplayTimer = null;
var isAutoplay = false;
var EXCLUDE_SEL = '.no-imgbox,.no-lightbox,.avatar,.img-icon,.avatar-badge,.smilie-icon,.aksrt-avatar,.aksrt-lazy-skeleton';
var TRIGGER_SEL = '.entry-content img, .wp-posts-content img, .aksrt-lightbox-trigger img, [data-aksrt-lightbox] img, .imgbox-container img, .comment-content .box-img img, a[data-imgbox] img';
var CONTAINER_SEL = '.entry-content, .wp-posts-content, .aksrt-lightbox-trigger, [data-aksrt-lightbox], .imgbox-container, .comment-content';
var MAX_ZOOM_DESKTOP = 2;
var MAX_ZOOM_MOBILE = 3;
function lightboxFlag(key, fallback) {
var w = window._win || (window.aksrtData && window.aksrtData.imgbox) || {};
if (w[key] === undefined || w[key] === null || w[key] === '') return fallback;
return w[key] === true || w[key] === 1 || w[key] === '1';
}
function lightboxEnabled() {
return lightboxFlag('imgbox', true);
}
function resolveFullUrl(img) {
var full = img.getAttribute('data-full-url') || img.getAttribute('data-src') || '';
if (full) return full;
var src = img.src || '';
// thumbnail → original: strip -NNNxNNN suffix
return src.replace(/-\d+x\d+(\.[a-z]+)$/i, '$1');
}
function isNoLightbox(img) {
if (img.closest(EXCLUDE_SEL)) return true;
// parent anchor that does NOT point to an image
var parentA = img.closest('a');
if (parentA) {
var href = parentA.getAttribute('href') || '';
if (href && !/\.(jpe?g|png|gif|webp|avif|svg|bmp)(\?|$)/i.test(href) && !parentA.hasAttribute('data-imgbox')) {
return true;
}
}
return false;
}
function createLightbox() {
if (!lightboxEnabled()) return;
if (lightboxEl) return;
lightboxEl = document.createElement('div');
lightboxEl.className = 'aksrt-lightbox';
lightboxEl.innerHTML =
'<div class="aksrt-lightbox__toolbar">' +
'<span class="aksrt-lightbox__counter"></span>' +
'<button type="button" class="aksrt-lightbox__tool toggle-zoom" title="缩放"><i class="fas fa-search-plus"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool toggle-thumbs" title="缩略图"><i class="fas fa-th"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool toggle-play" title="幻灯片"><i class="fas fa-play"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool toggle-full" title="全屏"><i class="fas fa-expand"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool aksrt-lightbox__download" title="下载"><i class="fas fa-download"></i></button>' +
'<button type="button" class="aksrt-lightbox__tool aksrt-lightbox__close" title="关闭"><i class="fas fa-times"></i></button>' +
'</div>' +
'<button type="button" class="aksrt-lightbox__nav aksrt-lightbox__nav--prev" aria-label="上一张"><i class="fas fa-chevron-left"></i></button>' +
'<div class="aksrt-lightbox__stage"><img class="aksrt-lightbox__img" src="" alt=""></div>' +
'<button type="button" class="aksrt-lightbox__nav aksrt-lightbox__nav--next" aria-label="下一张"><i class="fas fa-chevron-right"></i></button>' +
'<div class="aksrt-lightbox__caption"></div>' +
'<div class="aksrt-lightbox__thumbs" hidden></div>';
document.body.appendChild(lightboxEl);
lightboxImg = lightboxEl.querySelector('.aksrt-lightbox__img');
lightboxCaption = lightboxEl.querySelector('.aksrt-lightbox__caption');
lightboxCounter = lightboxEl.querySelector('.aksrt-lightbox__counter');
lightboxThumbsWrap = lightboxEl.querySelector('.aksrt-lightbox__thumbs');
// Apply window._win imgbox feature flags
var zoomBtn = lightboxEl.querySelector('.toggle-zoom');
var thumbsBtn = lightboxEl.querySelector('.toggle-thumbs');
var playBtn = lightboxEl.querySelector('.toggle-play');
var fullBtn = lightboxEl.querySelector('.toggle-full');
var downBtn = lightboxEl.querySelector('.aksrt-lightbox__download');
if (zoomBtn) zoomBtn.hidden = !lightboxFlag('imgbox_zoom', true);
if (thumbsBtn) thumbsBtn.hidden = !lightboxFlag('imgbox_thumbs', true);
if (playBtn) playBtn.hidden = !lightboxFlag('imgbox_play', false);
if (fullBtn) fullBtn.hidden = !lightboxFlag('imgbox_full', true);
if (downBtn) downBtn.hidden = !lightboxFlag('imgbox_down', false);
// Close
lightboxEl.querySelector('.aksrt-lightbox__close').addEventListener('click', function (e) { e.stopPropagation(); closeLightbox(); });
lightboxEl.addEventListener('click', function (e) { if (e.target === lightboxEl || e.target.classList.contains('aksrt-lightbox__stage')) closeLightbox(); });
// Nav
lightboxEl.querySelector('.aksrt-lightbox__nav--prev').addEventListener('click', function (e) { e.stopPropagation(); navigateLightbox(-1); });
lightboxEl.querySelector('.aksrt-lightbox__nav--next').addEventListener('click', function (e) { e.stopPropagation(); navigateLightbox(1); });
// Keyboard
document.addEventListener('keydown', function (e) {
if (!lightboxEl.classList.contains('is-open')) return;
if (e.key === 'Escape') closeLightbox();
if (e.key === 'ArrowLeft') navigateLightbox(-1);
if (e.key === 'ArrowRight') navigateLightbox(1);
});
// Mousewheel nav
lightboxEl.addEventListener('wheel', function (e) {
if (!lightboxEl.classList.contains('is-open')) return;
if (currentZoom > 1) return; // don't nav when zoomed
e.preventDefault();
if (e.deltaY > 0) navigateLightbox(1);
else navigateLightbox(-1);
}, { passive: false });
// Image click → toggle zoom
lightboxImg.addEventListener('click', function (e) { e.stopPropagation(); toggleZoom(); });
// Double-click → zoom in
lightboxImg.addEventListener('dblclick', function (e) {
e.stopPropagation();
var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP;
if (currentZoom < maxZoom) setZoom(maxZoom);
else setZoom(1);
});
// Pinch zoom (mobile)
var pinchStart = 0;
lightboxEl.addEventListener('touchstart', function (e) {
if (e.touches.length === 2) {
pinchStart = Math.hypot(
e.touches[1].clientX - e.touches[0].clientX,
e.touches[1].clientY - e.touches[0].clientY
);
}
}, { passive: true });
lightboxEl.addEventListener('touchmove', function (e) {
if (e.touches.length === 2 && pinchStart > 0) {
var dist = Math.hypot(
e.touches[1].clientX - e.touches[0].clientX,
e.touches[1].clientY - e.touches[0].clientY
);
var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP;
var newZoom = Math.min(maxZoom, Math.max(1, currentZoom * (dist / pinchStart)));
setZoom(newZoom);
pinchStart = dist;
}
}, { passive: true });
lightboxEl.addEventListener('touchend', function () { pinchStart = 0; }, { passive: true });
// Touch swipe/pull-dismiss (skip when zoomed)
var touchStartY = 0, touchStartX = 0, isSwiping = false;
lightboxEl.addEventListener('touchstart', function (e) {
if (e.touches.length !== 1 || currentZoom > 1.05) return;
touchStartY = e.touches[0].clientY;
touchStartX = e.touches[0].clientX;
isSwiping = false;
}, { passive: true });
lightboxEl.addEventListener('touchmove', function (e) {
if (e.touches.length !== 1 || currentZoom > 1.05) return;
var dy = e.touches[0].clientY - touchStartY;
var dx = e.touches[0].clientX - touchStartX;
if (Math.abs(dy) > Math.abs(dx) && dy > 30) {
isSwiping = true;
var opacity = Math.max(0.2, (150 - dy) / 80);
lightboxImg.style.transform = 'translate3d(0, ' + dy + 'px, 0) scale(' + currentZoom + ')';
lightboxImg.style.opacity = String(Math.min(1, opacity));
}
}, { passive: true });
lightboxEl.addEventListener('touchend', function () {
if (isSwiping) {
closeLightbox();
lightboxImg.style.transform = '';
lightboxImg.style.opacity = '';
}
isSwiping = false;
}, { passive: true });
// Download
lightboxEl.querySelector('.aksrt-lightbox__download').addEventListener('click', function (e) {
e.stopPropagation();
var item = currentGallery[currentIndex];
if (!item || !item.src) return;
var a = document.createElement('a');
var fullSrc = item.fullSrc || item.src;
a.href = fullSrc;
a.download = fullSrc.substring(fullSrc.lastIndexOf('/') + 1) || 'image';
document.body.appendChild(a);
a.click();
a.remove();
});
// Zoom button
lightboxEl.querySelector('.toggle-zoom').addEventListener('click', function (e) { e.stopPropagation(); toggleZoom(); });
// Thumbnails toggle
lightboxEl.querySelector('.toggle-thumbs').addEventListener('click', function (e) {
e.stopPropagation();
lightboxThumbsWrap.hidden = !lightboxThumbsWrap.hidden;
});
// Slideshow toggle
lightboxEl.querySelector('.toggle-play').addEventListener('click', function (e) {
e.stopPropagation();
toggleAutoplay();
});
// Fullscreen
lightboxEl.querySelector('.toggle-full').addEventListener('click', function (e) {
e.stopPropagation();
toggleFullscreen();
});
document.addEventListener('fullscreenchange', function () {
var icon = lightboxEl.querySelector('.toggle-full i');
if (document.fullscreenElement) {
lightboxEl.classList.add('full-screen');
icon.className = 'fas fa-compress';
} else {
lightboxEl.classList.remove('full-screen');
icon.className = 'fas fa-expand';
}
});
}
function setZoom(z) {
currentZoom = z;
lightboxImg.style.transform = 'scale(' + z + ')';
lightboxEl.classList.toggle('is-zoomed', z > 1);
}
function toggleZoom() {
var maxZoom = window.innerWidth < 768 ? MAX_ZOOM_MOBILE : MAX_ZOOM_DESKTOP;
setZoom(currentZoom > 1 ? 1 : maxZoom);
}
function toggleAutoplay() {
var icon = lightboxEl.querySelector('.toggle-play i');
if (isAutoplay) {
clearInterval(autoplayTimer);
isAutoplay = false;
icon.className = 'fas fa-play';
} else {
if (currentGallery.length < 2) return;
isAutoplay = true;
icon.className = 'fas fa-pause';
autoplayTimer = setInterval(function () { navigateLightbox(1); }, 3000);
}
}
function toggleFullscreen() {
var rfs = lightboxEl.requestFullscreen || lightboxEl.webkitRequestFullscreen || lightboxEl.msRequestFullscreen;
if (document.fullscreenElement || document.webkitFullscreenElement) {
(document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document);
} else if (rfs) {
rfs.call(lightboxEl);
}
}
function buildThumbnails() {
if (!lightboxThumbsWrap) return;
lightboxThumbsWrap.innerHTML = '';
currentGallery.forEach(function (item, i) {
var thumb = document.createElement('img');
thumb.src = item.src;
thumb.className = 'aksrt-lightbox__thumb' + (i === currentIndex ? ' is-active' : '');
thumb.addEventListener('click', function (e) {
e.stopPropagation();
showLightboxImage(i);
});
lightboxThumbsWrap.appendChild(thumb);
});
}
function syncThumbActive() {
if (!lightboxThumbsWrap) return;
var thumbs = lightboxThumbsWrap.querySelectorAll('.aksrt-lightbox__thumb');
thumbs.forEach(function (t, i) { t.classList.toggle('is-active', i === currentIndex); });
var active = thumbs[currentIndex];
if (active) active.scrollIntoView({ inline: 'center', block: 'nearest', behavior: 'smooth' });
}
function openLightbox(src, caption, gallery, index, originEl) {
createLightbox();
currentGallery = gallery || [{ src: src, caption: caption || '' }];
currentIndex = index || 0;
currentZoom = 1;
// Origin animation
if (originEl) {
var rect = originEl.getBoundingClientRect();
lightboxEl.style.setProperty('--imgbox-origin-top', (rect.top + rect.height / 2) + 'px');
lightboxEl.style.setProperty('--imgbox-origin-left', (rect.left + rect.width / 2) + 'px');
lightboxEl.classList.add('from-origin');
} else {
lightboxEl.classList.remove('from-origin');
}
showLightboxImage(currentIndex);
buildThumbnails();
lightboxEl.classList.add('is-open');
document.body.style.overflow = 'hidden';
lightboxEl.dispatchEvent(new CustomEvent('aksrt:lightbox-open', { bubbles: true }));
}
function showLightboxImage(index) {
if (index < 0) index = currentGallery.length - 1;
if (index >= currentGallery.length) index = 0;
currentIndex = index;
setZoom(1);
var item = currentGallery[currentIndex];
lightboxImg.src = item.fullSrc || item.src;
lightboxImg.alt = item.caption || '';
lightboxCaption.textContent = item.caption || '';
lightboxCaption.style.display = item.caption ? '' : 'none';
if (currentGallery.length > 1) {
lightboxCounter.textContent = (currentIndex + 1) + ' / ' + currentGallery.length;
lightboxCounter.style.display = '';
} else {
lightboxCounter.style.display = 'none';
}
syncThumbActive();
}
function navigateLightbox(dir) {
showLightboxImage(currentIndex + dir);
}
function closeLightbox() {
if (!lightboxEl) return;
if (isAutoplay) toggleAutoplay();
lightboxEl.classList.remove('is-open', 'is-zoomed', 'from-origin');
document.body.style.overflow = '';
lightboxThumbsWrap.hidden = true;
if (document.fullscreenElement) {
(document.exitFullscreen || document.webkitExitFullscreen || document.msExitFullscreen).call(document);
}
}
// Auto-bind: expanded trigger scope + exclusion rules
function initLightbox() {
document.addEventListener('click', function (e) {
// Also support a[data-imgbox]
var anchor = e.target.closest('a[data-imgbox]');
if (anchor) {
e.preventDefault();
var href = anchor.getAttribute('href') || anchor.getAttribute('data-imgbox');
openLightbox(href, '', [{ src: href, fullSrc: href, caption: '' }], 0, anchor);
return;
}
var img = e.target.closest(TRIGGER_SEL);
if (!img) return;
if (img.width < 100 || img.height < 100) return;
if (!lightboxEnabled() || isNoLightbox(img)) return;
e.preventDefault();
var container = img.closest(CONTAINER_SEL);
if (!container) return;
var allImages = container.querySelectorAll('img');
var gallery = [];
var foundIndex = 0;
allImages.forEach(function (el) {
if (el.width < 100 || el.height < 100) return;
if (isNoLightbox(el)) return;
var full = resolveFullUrl(el);
var caption = el.alt || '';
var figcaption = el.closest('figure') ? el.closest('figure').querySelector('figcaption') : null;
if (figcaption) caption = figcaption.textContent;
gallery.push({ src: el.dataset.src || el.src, fullSrc: full, caption: caption });
if (el === img) foundIndex = gallery.length - 1;
});
if (gallery.length > 0) {
openLightbox(img.dataset.src || img.src, img.alt, gallery, foundIndex, img);
}
});
}
// 暴露到全局
window.AksrtLightbox = {
open: openLightbox,
close: closeLightbox,
};
/* ============================================================
* 3. 滚动方向感知
* ============================================================ */
// Scroll direction is now handled by aksrt-runtime.js body scroll bus
// (body.scroll-down / body.scroll-up / body.body-scroll).
// This stub syncs the legacy aksrt-* prefixed classes for backward compat.
function initScrollDirection() {
var body = document.body;
function sync() {
body.classList.toggle('aksrt-body-scroll', body.classList.contains('body-scroll'));
body.classList.toggle('aksrt-scroll-down', body.classList.contains('scroll-down'));
body.classList.toggle('aksrt-scroll-up', body.classList.contains('scroll-up'));
body.classList.add('aksrt-scrolling');
clearTimeout(body._aksrtScrollTimer);
body._aksrtScrollTimer = setTimeout(function () {
body.classList.remove('aksrt-scrolling');
}, 300);
}
function onScroll() {
requestAnimationFrame(sync);
}
window.addEventListener('scroll', onScroll, { passive: true });
}
/* ============================================================
* 4. 导航溢出自动折叠
* 菜单项超出容器宽度时折叠进「更多」下拉
* ============================================================ */
function initNavOverflow() {
var nav = document.querySelector('.site-nav .nav-menu, .site-nav .primary-nav, .header-nav__menu');
if (!nav) return;
// 创建「更多」按钮
var moreLi = document.createElement('li');
moreLi.className = 'nav-item aksrt-nav-more';
moreLi.style.display = 'none';
moreLi.innerHTML =
'<button type="button" class="aksrt-nav-more__btn" aria-haspopup="true" aria-expanded="false">更多 <i class="fas fa-caret-down"></i></button>' +
'<ul class="aksrt-nav-more__dropdown"></ul>';
nav.appendChild(moreLi);
var moreBtn = moreLi.querySelector('.aksrt-nav-more__btn');
var dropdown = moreLi.querySelector('.aksrt-nav-more__dropdown');
moreBtn.addEventListener('click', function (e) {
e.stopPropagation();
var open = dropdown.classList.toggle('is-open');
moreBtn.setAttribute('aria-expanded', open ? 'true' : 'false');
});
document.addEventListener('click', function () {
dropdown.classList.remove('is-open');
moreBtn.setAttribute('aria-expanded', 'false');
});
function checkOverflow() {
var items = Array.from(nav.children).filter(function (el) {
return !el.classList.contains('aksrt-nav-more');
});
// 重置:把所有项移回原位
while (dropdown.firstChild) {
var li = dropdown.firstChild;
nav.insertBefore(li, moreLi);
}
moreLi.style.display = 'none';
var navWidth = nav.clientWidth;
var moreWidth = 80; // 预留「更多」按钮宽度
var usedWidth = 0;
var overflowItems = [];
items.forEach(function (item) {
usedWidth += item.offsetWidth;
if (usedWidth + moreWidth > navWidth) {
overflowItems.push(item);
}
});
if (overflowItems.length > 0) {
moreLi.style.display = '';
overflowItems.forEach(function (item) {
dropdown.appendChild(item);
});
}
}
// ResizeObserver 监听(避免 resize 抖动)
if ('ResizeObserver' in window) {
var timer = null;
var ro = new ResizeObserver(function () {
if (timer) clearTimeout(timer);
timer = setTimeout(checkOverflow, 100);
});
ro.observe(nav);
} else {
window.addEventListener('resize', checkOverflow, { passive: true });
}
// 初始检测
checkOverflow();
}
/* ============================================================
* 初始化入口
* ============================================================ */
document.addEventListener('DOMContentLoaded', function () {
initLightbox();
initScrollDirection();
initNavOverflow();
});
})();