/** * AKSRT Effects — count-up / countdown / marquee / 3D tilt / FPS detection * * 全部渐进增强,尊重 prefers-reduced-motion。 * * @package AKSRT */ (function () { 'use strict'; var reducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; var countUpObserver = null; var countdownElements = []; var countdownTimer = null; var fpsDetectionStarted = false; function scopeFor(root) { return root && root.querySelectorAll ? root : document; } function eachMatch(root, selector, callback) { var scope = scopeFor(root); if (scope.nodeType === 1 && scope.matches(selector)) callback(scope); scope.querySelectorAll(selector).forEach(callback); } function animateCountUp(el) { if (el.dataset.aksrtCountupBound === '1') return; el.dataset.aksrtCountupBound = '1'; countUpObserver.observe(el); } function initCountUp(root) { if (reducedMotion || !('IntersectionObserver' in window)) return; if (!countUpObserver) { countUpObserver = new IntersectionObserver(function (entries) { entries.forEach(function (entry) { if (!entry.isIntersecting) return; var el = entry.target; countUpObserver.unobserve(el); var target = parseFloat(el.dataset.target || '0'); var duration = parseInt(el.dataset.duration || '2000', 10); var decimals = (el.dataset.decimals || '0|').split('|'); var prefix = decimals[1] || ''; var dec = parseInt(decimals[0], 10) || 0; var startTime = null; function step(timestamp) { if (!startTime) startTime = timestamp; var progress = Math.min((timestamp - startTime) / duration, 1); var current = target * (1 - Math.pow(1 - progress, 3)); el.textContent = prefix + current.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ','); if (progress < 1) requestAnimationFrame(step); else el.textContent = prefix + target.toFixed(dec).replace(/\B(?=(\d{3})+(?!\d))/g, ','); } requestAnimationFrame(step); }); }, { threshold: 0.3 }); } eachMatch(root, '[data-aksrt-countup]', animateCountUp); } function pad(n) { return n < 10 ? '0' + n : '' + n; } function updateCountdowns() { var now = Date.now(); countdownElements = countdownElements.filter(function (el) { return document.documentElement.contains(el); }); countdownElements.forEach(function (el) { var end = new Date(el.dataset.end).getTime(); var diff = end - now; if (diff <= 0) { el.textContent = el.dataset.expired || '已结束'; return; } var d = Math.floor(diff / 86400000); var h = Math.floor((diff % 86400000) / 3600000); var m = Math.floor((diff % 3600000) / 60000); var s = Math.floor((diff % 60000) / 1000); var format = el.dataset.format || 'dhms'; if (format === 'dhms') el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + ':' + pad(m) + ':' + pad(s); else if (format === 'hms') el.textContent = pad(h + d * 24) + ':' + pad(m) + ':' + pad(s); else el.textContent = (d > 0 ? d + '天 ' : '') + pad(h) + '时' + pad(m) + '分' + pad(s) + '秒'; }); } function initCountdown(root) { eachMatch(root, '[data-aksrt-countdown]', function (el) { if (el.dataset.aksrtCountdownBound === '1') return; el.dataset.aksrtCountdownBound = '1'; countdownElements.push(el); }); updateCountdowns(); if (!countdownTimer && countdownElements.length) countdownTimer = window.setInterval(updateCountdowns, 1000); } function initTilt(root) { if (reducedMotion) return; eachMatch(root, '[data-aksrt-tilt]', function (el) { if (el.dataset.tiltBound) return; el.dataset.tiltBound = '1'; var maxDeg = parseFloat(el.dataset.tiltMax || '10'); var perspective = parseFloat(el.dataset.tiltPerspective || '800'); var scale = parseFloat(el.dataset.tiltScale || '1.03'); var glare = el.dataset.tiltGlare === '1'; var glareEl = null; if (glare) { glareEl = document.createElement('div'); glareEl.style.cssText = 'position:absolute;inset:0;border-radius:inherit;pointer-events:none;background:linear-gradient(135deg,rgba(255,255,255,0) 0%,rgba(255,255,255,0.25) 100%);opacity:0;transition:opacity 0.2s;'; el.style.position = el.style.position || 'relative'; el.style.overflow = 'hidden'; el.appendChild(glareEl); } el.addEventListener('mousemove', function (e) { var rect = el.getBoundingClientRect(); var x = (e.clientX - rect.left) / rect.width; var y = (e.clientY - rect.top) / rect.height; el.style.transform = 'perspective(' + perspective + 'px) rotateX(' + ((0.5 - y) * maxDeg) + 'deg) rotateY(' + ((x - 0.5) * maxDeg) + 'deg) scale3d(' + scale + ',' + scale + ',' + scale + ')'; if (glareEl) glareEl.style.opacity = '1'; }); el.addEventListener('mouseleave', function () { el.style.transform = 'perspective(' + perspective + 'px) rotateX(0deg) rotateY(0deg) scale3d(1,1,1)'; if (glareEl) glareEl.style.opacity = '0'; }); }); } function initFpsDetection(root, reason) { if (fpsDetectionStarted || !('requestAnimationFrame' in window)) return; fpsDetectionStarted = true; var frames = 0; var startTime = performance.now(); function countFrames() { frames++; var elapsed = performance.now() - startTime; if (elapsed >= 1000) { if (Math.round(frames * 1000 / elapsed) < 24 && document.body) document.body.classList.add('fps-accelerat'); return; } requestAnimationFrame(countFrames); } requestAnimationFrame(countFrames); } function init(root, reason) { initCountUp(root); initCountdown(root); if (typeof initMarquee === 'function') initMarquee(root, reason); initTilt(root); initFpsDetection(root, reason); } window.AksrtFrontend.register('aksrt-effects', init); })();