Files
wordpress-theme-aksrt/assets/widgets/frontend.js
T

386 lines
17 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.
(function () {
'use strict';
var config = window.aksrtWidgets || {};
var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
function once(el, key) {
var name = 'aksrt' + key;
if (el.dataset[name]) return false;
el.dataset[name] = '1';
return true;
}
function moveTabFocus(event, root) {
var current = event.target.closest('[role="tab"]');
if (!current) return;
var tabs = Array.prototype.slice.call(root.querySelectorAll('[role="tab"]'));
var index = tabs.indexOf(current);
var next = index;
if (event.key === 'ArrowRight') next = (index + 1) % tabs.length;
else if (event.key === 'ArrowLeft') next = (index - 1 + tabs.length) % tabs.length;
else if (event.key === 'Home') next = 0;
else if (event.key === 'End') next = tabs.length - 1;
else return;
event.preventDefault();
tabs[next].focus();
tabs[next].click();
}
function videoType(url) {
if (/\.m3u8(?:\?|$)/i.test(url)) return 'hls';
if (/\.flv(?:\?|$)/i.test(url)) return 'flv';
if (/\.mpd(?:\?|$)/i.test(url)) return 'dash';
return 'auto';
}
function initVideo(el) {
// 幂等标记落在 data-aksrt-widget-video;data-aksrt-video 是
// AksrtVideoPlayer 的自动初始化契约,被扫到会在容器里再挂一个无源播放器。
if (!once(el, 'WidgetVideo')) return;
var fallback = el.querySelector('video');
if (fallback) {
fallback.addEventListener('error', function () {
var message = el.querySelector('.aksrt-widget-media-error');
if (message) message.hidden = false;
});
}
function mountPlayer(event) {
if (typeof window.DPlayer !== 'function' || el.aksrtPlayer) return;
if (event) event.preventDefault();
var holder = document.createElement('div');
holder.className = 'aksrt-widget-video__player';
el.insertBefore(holder, fallback || el.firstChild);
try {
el.aksrtPlayer = new window.DPlayer({
container: holder,
autoplay: el.dataset.autoplay === '1' && !reducedMotion,
video: {
url: el.dataset.videoUrl || '',
pic: el.dataset.videoPoster || '',
type: videoType(el.dataset.videoUrl || '')
}
});
if (el.dataset.muted === '1' && el.aksrtPlayer.video) el.aksrtPlayer.video.muted = true;
if (fallback) fallback.hidden = true;
} catch (error) {
holder.remove();
}
}
if (fallback && fallback.classList.contains('lazyload')) {
fallback.addEventListener('lazybeforeunveil', mountPlayer, { once: true });
} else {
mountPlayer();
}
}
function initQuote(figure) {
if (!once(figure, 'Quote')) return;
if (window.AksrtYiyan) window.AksrtYiyan.init(figure);
}
function errorMarkup(page, append) {
var i18n = config.i18n || {};
return '<div class="aksrt-widget-request-error" role="alert"><span>' + (i18n.error || '加载失败,请重试。') + '</span><button type="button" data-widget-retry data-retry-page="' + page + '" data-retry-append="' + (append ? '1' : '0') + '">' + (i18n.retry || '重试') + '</button></div>';
}
function itemsUrl(widget, instance, page, segment) {
var url = new URL(config.restUrl, window.location.href);
var path = '/' + encodeURIComponent(widget) + '/items';
if (url.searchParams.has('rest_route')) {
url.searchParams.set('rest_route', url.searchParams.get('rest_route').replace(/\/$/, '') + path);
} else {
url.pathname = url.pathname.replace(/\/$/, '') + path;
}
url.searchParams.set('instance', instance);
url.searchParams.set('page', page);
if (segment !== undefined && segment !== '') url.searchParams.set('segment', segment);
return url.href;
}
function loaderOf(container) {
if (!container) return null;
return container.querySelector('.post_ajax_loader');
}
function fallbackLoader() {
var text = (window.aksrtData && aksrtData.ajaxLoading) || '加载中...';
var node = document.createElement('div');
node.className = 'post_ajax_loader';
node.setAttribute('role', 'status');
node.innerHTML = '<div class="mb20"><div class="text-center muted-2-color mt20"><i class="loading mr10"></i>' + text + '</div></div>';
return node;
}
function setBusy(container, busy) {
if (!container) return;
if (busy) {
container.dataset.loading = '1';
container.setAttribute('aria-busy', 'true');
// 骨架开关走 .is-loading 类(modal-runtime.css),不写 inline display。
var loader = loaderOf(container) || container.appendChild(fallbackLoader());
loader.classList.add('is-loading');
} else {
delete container.dataset.loading;
container.setAttribute('aria-busy', 'false');
var loader = loaderOf(container);
if (loader) loader.classList.remove('is-loading');
}
}
// 追加时骨架紧跟已有条目;替换时先清空旧页,骨架留在新页的位置。
// 请求成功后隐藏并保留同一个骨架节点,供下一轮请求复用。
function anchorLoader(container) {
var pag = container.querySelector(':scope > .ajax-pag');
var loader = loaderOf(container);
if (!loader) return null;
if (pag) {
pag.parentNode.insertBefore(loader, pag);
} else {
container.appendChild(loader);
}
return loader;
}
function loadItems(container, page, append) {
var owner = container.closest('[data-widget]') || container;
var widget = owner.dataset.widget || container.dataset.widget;
var instance = owner.dataset.instance || container.dataset.instance;
var segment = container.dataset.segment || owner.dataset.segment || '';
if (!widget || !instance || !config.restUrl || container.dataset.loading === '1') return;
container.querySelectorAll(':scope > .aksrt-widget-request-error').forEach(function (error) { error.remove(); });
setBusy(container, true);
var previous = [];
if (!append) {
// 对齐参考主题 beforeSend:数字分页与跳页先移除旧内容,而非等待响应。
// 暂存原节点,只在请求失败时恢复内容和分页控件。
var loader = loaderOf(container);
Array.prototype.slice.call(container.children).forEach(function (child) {
if (child === loader) return;
previous.push(child);
child.remove();
});
}
anchorLoader(container);
return fetch(itemsUrl(widget, instance, page, segment), { credentials: 'same-origin', headers: { Accept: 'application/json' } })
.then(function (response) { if (!response.ok) throw new Error('items'); return response.json(); })
.then(function (data) {
var fragment = document.createElement('div');
fragment.innerHTML = String(data.html || '');
var anchor = loaderOf(container) || container.querySelector(':scope > .ajax-pag');
Array.prototype.slice.call(fragment.children).forEach(function (node) {
if (anchor && anchor.parentNode === container) {
container.insertBefore(node, anchor);
} else {
container.appendChild(node);
}
});
Array.prototype.slice.call(container.querySelectorAll(':scope > .ajax-pag')).forEach(function (node) { node.remove(); });
if (data.paginate_html) container.insertAdjacentHTML('beforeend', data.paginate_html);
container.dataset.page = String(data.page || page);
container.dataset.loaded = '1';
anchorLoader(container);
previous = [];
scan(container);
if (window.jQuery) window.jQuery(document).trigger('auto_fun');
})
.catch(function () {
var loader = loaderOf(container);
previous.forEach(function (node) { container.insertBefore(node, loader); });
anchorLoader(container);
if (loader && loader.parentNode === container) {
loader.insertAdjacentHTML('beforebegin', errorMarkup(page, append));
} else {
container.insertAdjacentHTML('afterbegin', errorMarkup(page, append));
}
})
.finally(function () { setBusy(container, false); });
}
function initItems(container) {
if (!once(container, 'Items')) return;
if (container.hasAttribute('data-widget-lazy') && container.dataset.loaded !== '1') {
container.addEventListener('lazybeforeunveil', function (event) {
if (event.target === container && container.dataset.loaded !== '1') loadItems(container, 1, false);
});
// main.js loads asynchronously through loader_js. Its lazySizes scan
// may finish before this client binds, so consume an unveiled pager.
if (!container.classList.contains('lazyload')) loadItems(container, 1, false);
}
function jumpTo(input) {
var page = Math.min(Number(input.max), Math.floor(Number(input.value)));
if (!Number.isFinite(page) || page < 1 || page === Number(container.dataset.page || 1)) {
input.focus();
return;
}
loadItems(container, page, false);
}
container.addEventListener('input', function (event) {
if (!event.target.matches('[data-widget-jump]')) return;
event.stopPropagation();
var page = Math.floor(Number(event.target.value));
event.target.value = page > 0 ? String(Math.min(Number(event.target.max), page)) : '';
});
container.addEventListener('keydown', function (event) {
if (event.key !== 'Enter' || !event.target.matches('[data-widget-jump]')) return;
event.preventDefault();
event.stopPropagation();
jumpTo(event.target);
});
container.addEventListener('click', function (event) {
var jump = event.target.closest('[data-widget-jump-go]');
if (jump) {
event.preventDefault();
event.stopPropagation();
jumpTo(jump.parentElement.querySelector('[data-widget-jump]'));
return;
}
var retry = event.target.closest('[data-widget-retry]');
var trigger = event.target.closest('[data-next-page]');
var pageLink = event.target.closest('.page-numbers[data-widget-page]');
if (retry) {
event.preventDefault();
loadItems(container, Number(retry.dataset.retryPage || 1), retry.dataset.retryAppend === '1');
return;
}
if (trigger) {
event.preventDefault();
loadItems(container, Number(trigger.dataset.nextPage || 2), true);
return;
}
if (pageLink) {
event.preventDefault();
loadItems(container, Number(pageLink.dataset.widgetPage || 1), false);
}
});
}
function initTabs(tabs) {
if (!once(tabs, 'Tabs')) return;
tabs.addEventListener('keydown', function (event) { moveTabFocus(event, tabs); });
tabs.addEventListener('click', function (event) {
var button = event.target.closest('[role="tab"][data-segment]');
if (!button) return;
event.preventDefault();
var nav = button.closest('[role="tablist"]');
if (nav && nav.scrollWidth > nav.clientWidth) {
var tabBounds = button.getBoundingClientRect();
var navBounds = nav.getBoundingClientRect();
nav.scrollTo({ left: nav.scrollLeft + tabBounds.left - navBounds.left + tabBounds.width / 2 - nav.clientWidth / 2, behavior: reducedMotion ? 'auto' : 'smooth' });
}
tabs.querySelectorAll('[role="tab"]').forEach(function (tab) {
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
if (tab.parentElement) tab.parentElement.classList.toggle('active', tab === button);
});
tabs.querySelectorAll('.tab-content > .tab-pane').forEach(function (item) {
var active = item.id === button.getAttribute('aria-controls');
item.classList.toggle('active', active); item.classList.toggle('in', active);
});
var panel = tabs.querySelector('[data-widget-items][data-segment="' + button.dataset.segment + '"]');
if (!panel) return;
if (panel.dataset.loaded !== '1') loadItems(panel, 1, false);
});
}
function initFaq(faq) {
if (!once(faq, 'Faq')) return;
faq.addEventListener('click', function (event) {
var button = event.target.closest('.aksrt-faq__item > button');
if (!button) return;
var expanded = button.getAttribute('aria-expanded') === 'true';
button.setAttribute('aria-expanded', expanded ? 'false' : 'true');
var panel = document.getElementById(button.getAttribute('aria-controls'));
if (panel) panel.hidden = expanded;
});
var search = faq.querySelector('input[type="search"]');
if (search) search.addEventListener('input', function () {
var needle = search.value.trim().toLowerCase();
faq.querySelectorAll('.aksrt-faq__item').forEach(function (item) { item.hidden = needle && String(item.dataset.search || '').toLowerCase().indexOf(needle) === -1; });
});
}
function initFeatures(features) {
if (!once(features, 'Features')) return;
features.addEventListener('keydown', function (event) { moveTabFocus(event, features); });
features.addEventListener('click', function (event) {
var button = event.target.closest('[data-feature-index]');
if (!button) return;
var index = button.dataset.featureIndex;
features.querySelectorAll('[data-feature-index]').forEach(function (tab) {
tab.setAttribute('aria-selected', tab === button ? 'true' : 'false');
tab.setAttribute('tabindex', tab === button ? '0' : '-1');
});
features.querySelectorAll('[data-feature-panel]').forEach(function (panel) { panel.hidden = panel.dataset.featurePanel !== index; });
});
}
function initCount(el) {
if (!once(el, 'Count') || reducedMotion) return;
var raw = el.dataset.countUp || '';
var match = raw.match(/-?[0-9]+(?:\.[0-9]+)?/);
if (!match) return;
var target = Number(match[0]);
var prefix = raw.slice(0, match.index);
var suffix = raw.slice(match.index + match[0].length);
var start = performance.now();
function frame(now) {
var progress = Math.min(1, (now - start) / 900);
el.textContent = prefix + Math.round(target * (1 - Math.pow(1 - progress, 3))) + suffix;
if (progress < 1) requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
}
/* 幻灯片加图文视频卡片:以左侧幻灯片实际高度重算右侧卡片比例。
* 合作伙伴滚动由运行时 main.js 初始化(.zib-partners-scroll 唯一所有者)。 */
function measureSliderGraphic(widget) {
if (once(widget, 'Combo')) return;
var slider = widget.querySelector('.slider-col .zib-slider') || widget.querySelector('.slider-col');
var covers = widget.querySelector('.cover-col');
if (!slider || !covers) return;
var row = Number(widget.dataset.row || 2);
var col = Number(widget.dataset.col || 2);
var gap = 10;
function update() {
var height = slider.offsetHeight;
var width = covers.offsetWidth;
if (!height || !width) return;
var ratio = (((height - (col - 1) * gap) / col) / ((width - (row - 1) * gap) / row)) * 100;
covers.style.setProperty('--cover-ratio', ratio.toFixed(2) + '%');
}
update();
window.addEventListener('resize', update, { passive: true });
if ('ResizeObserver' in window) new ResizeObserver(update).observe(slider);
}
function scan(root) {
var scope = root || document;
scope.querySelectorAll('.aksrt-widget-video[data-video-url]').forEach(initVideo);
scope.querySelectorAll('.aksrt-widget-quote').forEach(initQuote);
if (scope.matches && scope.matches('[data-widget-items]')) initItems(scope);
scope.querySelectorAll('[data-widget-items]').forEach(initItems);
scope.querySelectorAll('.widget-tab-post').forEach(initTabs);
scope.querySelectorAll('.aksrt-faq').forEach(initFaq);
scope.querySelectorAll('.aksrt-feature-intro').forEach(initFeatures);
scope.querySelectorAll('[data-count-up]').forEach(initCount);
scope.querySelectorAll('.widget-slider-graphic').forEach(measureSliderGraphic);
if (typeof window.VanillaTilt !== 'undefined') scope.querySelectorAll('[data-aksrt-tilt]').forEach(function (el) { if (once(el, 'Tilt')) window.VanillaTilt.init(el); });
}
function ready() {
scan(document);
if ('MutationObserver' in window) {
new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { if (node.nodeType === 1) scan(node); }); });
}).observe(document.body, { childList: true, subtree: true });
}
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', ready);
else ready();
// AJAX 注入新内容后由 auto_fun 重扫(once() data 标记保证幂等)。
if (window.jQuery) window.jQuery(document).on('auto_fun', function () { scan(document); });
})();