From 435ee1daa77b2e7eba75a8adbb63a62336c4a1b9 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 18 Aug 2026 01:33:38 +0800 Subject: [PATCH] feat(frontend): replace lazy loading with zibll lazysizes --- .gitignore | 1 + .../src/schema/sections/global/common.js | 50 +- .../enhancements/modal-motion/dark-mode-2.css | 70 +- .../css/frontend/header-components/base.css | 29 - .../css/frontend/home-modules/responsive.css | 16 - assets/css/frontend/homepage-fixed/base-1.css | 23 - assets/css/frontend/homepage-fixed/base-2.css | 8 - assets/css/frontend/pages/base-3.css | 16 - assets/js/aksrt-runtime.js | 21 - assets/js/core/pagination.js | 146 +-- assets/js/core/slider.js | 8 +- assets/js/credits.js | 2 +- assets/js/enhancements.js | 2 +- assets/js/header-inline-search.js | 29 +- assets/js/homepage-fixed.js | 48 - assets/js/lazysizes.js | 870 ++++++++++++++++++ assets/js/order-center.js | 4 +- assets/js/page-navs.js | 2 +- assets/js/pay-vip.js | 12 +- assets/js/user-badges.js | 4 +- assets/widgets/frontend.js | 53 +- footer.php | 2 +- inc/core-hooks/part-3.php | 2 +- inc/helpers/aksrt-avatar.php | 6 +- inc/helpers/aksrt-frontend-tags.php | 2 +- inc/helpers/aksrt-graphic-card.php | 18 +- inc/helpers/aksrt-icon-cards.php | 8 +- inc/helpers/aksrt-identity.php | 14 +- inc/helpers/aksrt-links.php | 4 +- inc/helpers/aksrt-null.php | 2 +- inc/helpers/aksrt-pay.php | 23 +- inc/helpers/aksrt-single-components.php | 6 +- inc/helpers/aksrt-single-render.php | 10 +- inc/helpers/aksrt-slider.php | 3 +- inc/helpers/aksrt-user-card.php | 2 +- inc/helpers/aksrt-vip.php | 2 +- inc/helpers/comments/content.php | 18 +- inc/helpers/comments/form-parts.php | 2 +- inc/helpers/header/aksrt-header-navbar.php | 8 +- inc/helpers/posts-renderer/config.php | 24 - inc/helpers/posts-renderer/markup.php | 9 +- inc/helpers/posts-renderer/post-data.php | 26 +- inc/helpers/template-tags/media-brand.php | 6 +- .../template-tags/pagination-permissions.php | 212 +++-- .../user-center-renderers/credits-vip.php | 2 +- .../user-center-renderers/profile-level.php | 2 +- .../user-center-renderers/sidebar-vip.php | 2 +- inc/helpers/user-center-renderers/vip-tab.php | 4 +- src/Admin/Schema/GlobalSchema.php | 45 +- src/Content/VipServiceModal.php | 2 +- src/Core/ScriptsPart1b.php | 8 + src/Core/ScriptsPart1c.php | 35 +- src/Frontend/NavWalker.php | 4 +- src/Settings/FacadeBulk.php | 37 - src/User/Level.php | 2 +- src/WidgetSystem/FormRenderer.php | 2 +- src/WidgetSystem/PostRenderer.php | 4 +- src/WidgetSystem/RendererHelpers.php | 11 +- .../Renderers/GeneralRenderer.php | 14 +- src/WidgetSystem/Renderers/SliderRenderer.php | 14 +- templates/pages/page-messages.php | 2 +- templates/parts/content-gallery.php | 3 +- templates/parts/content-post-card.php | 205 +---- templates/parts/docs/doc-index-card.php | 2 +- .../parts/home-modules/module-banner.php | 5 +- .../module-category_recommend_cards.php | 12 +- .../home-modules/module-friend_links.php | 2 +- templates/parts/home-modules/module-hero.php | 24 +- .../home-modules/module-highlight_cards.php | 2 +- templates/parts/home-modules/module-more.php | 2 +- .../home-modules/module-partner_marquee.php | 2 +- .../home-modules/module-product_showcase.php | 2 +- templates/parts/home-modules/module-user.php | 2 +- templates/parts/resource-download-box.php | 2 +- templates/parts/user-home/panel-follow.php | 2 +- templates/parts/user/author-hero.php | 4 +- templates/shortcodes/profile.php | 2 +- .../widget-system/lazyload-contract.test.cjs | 138 +++ 78 files changed, 1515 insertions(+), 909 deletions(-) create mode 100644 assets/js/lazysizes.js create mode 100644 tests/widget-system/lazyload-contract.test.cjs diff --git a/.gitignore b/.gitignore index ba52e376..88dea163 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ !/tests/widget-system/ /tests/widget-system/* !/tests/widget-system/post-visual-contract.test.cjs +!/tests/widget-system/lazyload-contract.test.cjs !/tests/visual/docker-compose.zibll.yml !/tests/visual/zibll-Caddyfile !/tests/visual/README.md diff --git a/assets/admin/src/schema/sections/global/common.js b/assets/admin/src/schema/sections/global/common.js index c940635b..8932989b 100644 --- a/assets/admin/src/schema/sections/global/common.js +++ b/assets/admin/src/schema/sections/global/common.js @@ -1,21 +1,24 @@ +const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri + ? String(window.aksrtAdminData.themeUri).replace(/\/$/, '') + : '' +const defaultThumbnail = themeRoot ? `${themeRoot}/assets/img/thumbnail.svg` : '' + export const commonSchema = { key: 'global.common', title: '常用功能', icon: 'fas fa-cog', description: '管理站点常用开关、懒加载与备案信息等基础配置。', defaults: { - lazyload_enabled: true, - lazyload_animation: 'blur', - lazyload_placeholder: '', - lazyload_blur: true, - lazyload_avatar: true, - lazyload_content: false, - lazyload_comment: true, - lazyload_private: true, - lazyload_slider: true, - lazyload_cover: true, - lazyload_other: true, - lazyload_widget: true, + lazy_posts_thumb: true, + lazy_avatar: true, + lazy_posts_content: false, + lazy_comment: true, + lazy_private: true, + lazy_sider: true, + lazy_cover: true, + lazy_other: true, + lazy_animation: 'blur', + thumbnail: defaultThumbnail, icp_filing: '', police_filing: '', footer_text: '', @@ -26,9 +29,9 @@ export const commonSchema = { title: '性能优化', icon: 'fas fa-tachometer-alt', fields: [ - { key: 'lazyload_enabled', label: '图片懒加载', type: 'switch', default: true, help: '开启后页面中的图片将在滚动到可见区域时才加载' }, + { key: 'lazy_posts_thumb', label: '文章缩略图懒加载', type: 'switch', default: true, help: '滚动到图像附近时再加载真实图片。' }, { - key: 'lazyload_animation', + key: 'lazy_animation', label: '懒加载动画', type: 'select', default: 'blur', @@ -37,10 +40,8 @@ export const commonSchema = { { label: '淡入', value: 'fade' }, { label: '缩放', value: 'scale' }, ], - dependsOn: { key: 'lazyload_enabled', value: true }, }, - { key: 'lazyload_placeholder', label: '预载图', type: 'media', default: '', dependsOn: { key: 'lazyload_enabled', value: true }, help: '自定义占位图,留空使用内置默认' }, - { key: 'lazyload_blur', label: '模糊预览', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true }, help: '加载前显示低分辨率模糊预览' }, + { key: 'thumbnail', label: '懒加载预载图', type: 'media', default: defaultThumbnail, help: '图片加载前显示的占位图。' }, ], }, { @@ -48,14 +49,13 @@ export const commonSchema = { title: '懒加载范围', icon: 'fas fa-layer-group', fields: [ - { key: 'lazyload_avatar', label: '头像懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_content', label: '文章内容图片懒加载', type: 'switch', default: false, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_comment', label: '评论内容图片懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_private', label: '私信聊天图片懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_slider', label: '幻灯片图片懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_cover', label: '封面图片懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_other', label: '其它图片懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, - { key: 'lazyload_widget', label: '小工具图片懒加载', type: 'switch', default: true, dependsOn: { key: 'lazyload_enabled', value: true } }, + { key: 'lazy_avatar', label: '头像懒加载', type: 'switch', default: true }, + { key: 'lazy_posts_content', label: '文章内容图片懒加载', type: 'switch', default: false }, + { key: 'lazy_comment', label: '评论内容图片懒加载', type: 'switch', default: true }, + { key: 'lazy_private', label: '私信聊天图片懒加载', type: 'switch', default: true }, + { key: 'lazy_sider', label: '幻灯片图片懒加载', type: 'switch', default: true }, + { key: 'lazy_cover', label: '封面图片懒加载', type: 'switch', default: true }, + { key: 'lazy_other', label: '其它图片懒加载', type: 'switch', default: true }, ], }, { diff --git a/assets/css/frontend/enhancements/modal-motion/dark-mode-2.css b/assets/css/frontend/enhancements/modal-motion/dark-mode-2.css index 63d1e334..71d1fc7b 100644 --- a/assets/css/frontend/enhancements/modal-motion/dark-mode-2.css +++ b/assets/css/frontend/enhancements/modal-motion/dark-mode-2.css @@ -69,53 +69,34 @@ } } -/* ============================================================ - * 14. 图片懒加载 blur-to-sharp 过渡 - * 模仿 zibll 的 lazy_blur 效果 - * ============================================================ */ -img.lazyload, -img.lazyloaded, -img[data-src] { - transition: filter 0.6s var(--ease), opacity 0.4s var(--ease); +/* Zibll lazySizes state and animation contract. */ +.lazyloaded { + transition: 0.3s; + --lazy-animation: lazy_blur; } -img.lazyload, -img[data-src] { - filter: blur(5px); - opacity: 0.7; +.lazyloaded.img-icon { + --lazy-animation: lazy_fade; } -img.lazyloaded { - filter: blur(0); - opacity: 1; +img.lazyloaded:not(.lazyloadafter) { + -webkit-animation: var(--lazy-animation) 0.6s; + animation: var(--lazy-animation) 0.6s; } -/* 14b. aksrt-lazy 动画变体(按 lazyload_animation 配置切换) */ -@keyframes aksrt-lazy-blur { - from { filter: blur(8px); opacity: 0.6; } - to { filter: blur(0); opacity: 1; } -} -@keyframes aksrt-lazy-fade { - from { opacity: 0; } - to { opacity: 1; } -} -@keyframes aksrt-lazy-scale { - from { transform: scale(1.08); opacity: 0.8; } - to { transform: scale(1); opacity: 1; } +@keyframes lazy_blur { + 0% { filter: blur(5px); } + 100% { filter: blur(0); } } -img.aksrt-lazy { - transition: filter 0.5s var(--ease), opacity 0.5s var(--ease), transform 0.5s var(--ease); +@keyframes lazy_fade { + 0% { opacity: 0; } + 100% { opacity: 1; } } -img.aksrt-loaded[data-lazy-animation="blur"] { - animation: aksrt-lazy-blur 0.6s ease both; -} -img.aksrt-loaded[data-lazy-animation="fade"] { - animation: aksrt-lazy-fade 0.6s ease both; -} -img.aksrt-loaded[data-lazy-animation="scale"] { - animation: aksrt-lazy-scale 0.5s ease both; +@keyframes lazy_scale { + 0% { transform: scale(1.1); } + 80% { transform: unset; } } /* ============================================================ @@ -308,21 +289,6 @@ input:focus, textarea:focus, select:focus, animation: aksrtPopIn var(--dur-fast) var(--ease-out) forwards; } -/* --- 图片懒加载过渡 --- */ -img.lazyload, img.lazyloaded, img[data-src] { - transition: filter 0.6s var(--ease), opacity 0.4s var(--ease); -} - -img.lazyload, img[data-src] { - filter: blur(5px); - opacity: 0.7; -} - -img.lazyloaded { - filter: blur(0); - opacity: 1; -} - /* --- 滚动进度条动效 --- */ .reading-progress-bar { transition: width 0.1s linear; diff --git a/assets/css/frontend/header-components/base.css b/assets/css/frontend/header-components/base.css index abf5253f..6073bded 100644 --- a/assets/css/frontend/header-components/base.css +++ b/assets/css/frontend/header-components/base.css @@ -43,35 +43,6 @@ object-fit: cover; } -/* Lazy preloader */ -.new-swiper .swiper-lazy-preloader { - position: absolute; - top: 50%; - left: 50%; - width: 42px; - height: 42px; - margin: -21px 0 0 -21px; - border: 3px solid rgba(255, 255, 255, 0.3); - border-top-color: #fff; - border-radius: 50%; - animation: aksrt-swiper-spin 0.8s linear infinite; -} - -@keyframes aksrt-swiper-spin { - from { transform: rotate(0deg); } - to { transform: rotate(360deg); } -} - -.new-swiper .swiper-lazy { - opacity: 0; - transition: opacity 0.3s; -} - -.new-swiper .swiper-lazy.lazyloaded, -.new-swiper .swiper-slide-visible .swiper-lazy { - opacity: 1; -} - .new-swiper .pagination-thumbnail-item { width: 60px; height: 40px; diff --git a/assets/css/frontend/home-modules/responsive.css b/assets/css/frontend/home-modules/responsive.css index 73b80ce2..c52ac1ec 100644 --- a/assets/css/frontend/home-modules/responsive.css +++ b/assets/css/frontend/home-modules/responsive.css @@ -256,21 +256,6 @@ flex-shrink: 0; } -/* 懒加载骨架 */ -.cat-card__banner .aksrt-skeleton { - position: absolute; - inset: 0; -} - -.cat-card__banner img.aksrt-lazy { - opacity: 0; - transition: opacity 0.3s ease; -} - -.cat-card__banner img.aksrt-loaded { - opacity: 1; -} - /* 响应式 */ @media (max-width: 767px) { .cat-cards-grid { @@ -291,4 +276,3 @@ /* ============================================================ * Phase 5: 运营型模块样式 * ============================================================ */ - diff --git a/assets/css/frontend/homepage-fixed/base-1.css b/assets/css/frontend/homepage-fixed/base-1.css index 81a6014b..e8fcff4d 100644 --- a/assets/css/frontend/homepage-fixed/base-1.css +++ b/assets/css/frontend/homepage-fixed/base-1.css @@ -15,21 +15,6 @@ 0. ANIMATIONS ================================================================ */ -@keyframes aksrt-lazy-blur { - from { filter: blur(5px); opacity: 0.6; } - to { filter: blur(0); opacity: 1; } -} - -@keyframes aksrt-lazy-fade { - from { opacity: 0; } - to { opacity: 1; } -} - -@keyframes aksrt-lazy-scale { - from { transform: scale(1.1); opacity: 0.8; } - to { transform: scale(1); opacity: 1; } -} - @keyframes aksrt-skeleton-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } @@ -55,14 +40,6 @@ transition: color 0.2s, background 0.3s, opacity 0.3s, box-shadow 0.3s, transform 0.3s; } -.aksrt-homepage img.aksrt-loaded { - animation: aksrt-lazy-blur 0.6s ease; -} - -.aksrt-homepage img.aksrt-loaded.img-icon { - animation: aksrt-lazy-fade 0.6s ease; -} - .aksrt-homepage .aksrt-skeleton { animation: aksrt-skeleton-pulse 1.5s ease-in-out infinite; } diff --git a/assets/css/frontend/homepage-fixed/base-2.css b/assets/css/frontend/homepage-fixed/base-2.css index 7ec18baf..cc181e3f 100644 --- a/assets/css/frontend/homepage-fixed/base-2.css +++ b/assets/css/frontend/homepage-fixed/base-2.css @@ -177,13 +177,6 @@ html.dark #index-tab-main > .posts-item.list:hover { transform: scale(1.02); } -.aksrt-homepage__posts > .posts-item.list .item-thumbnail .aksrt-skeleton { - position: absolute; - inset: 0; - background: var(--surface-2, #eee); - border-radius: inherit; -} - .aksrt-homepage__posts > .posts-item.list .item-thumbnail .img-badge { position: absolute; top: 8px; @@ -230,7 +223,6 @@ html.dark #index-tab-main > .posts-item.list:hover { border-radius: var(--radius, 8px); } -.aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span .aksrt-skeleton, .aksrt-homepage__posts > .posts-item.list.mult-thumb .thumb-items > span > img { position: absolute; inset: 0; diff --git a/assets/css/frontend/pages/base-3.css b/assets/css/frontend/pages/base-3.css index cc5da979..470330bc 100644 --- a/assets/css/frontend/pages/base-3.css +++ b/assets/css/frontend/pages/base-3.css @@ -26,21 +26,6 @@ text-decoration: underline; } -/* 分类推荐懒加载骨架 */ -.category-recommend-card__hero .aksrt-skeleton { - position: absolute; - inset: 0; -} - -.category-recommend-card__hero img.aksrt-lazy { - opacity: 0; - transition: opacity 0.3s ease; -} - -.category-recommend-card__hero img.aksrt-loaded { - opacity: 1; -} - /* 分类推荐响应式 */ @media (max-width: 767px) { .category-recommend { @@ -121,4 +106,3 @@ line-height: var(--leading-relaxed, 1.7); color: var(--color-ink-3); } - diff --git a/assets/js/aksrt-runtime.js b/assets/js/aksrt-runtime.js index 85fa4183..1f2d79e6 100644 --- a/assets/js/aksrt-runtime.js +++ b/assets/js/aksrt-runtime.js @@ -734,27 +734,6 @@ }); }); - // Lazy images - root.querySelectorAll('img.aksrt-lazy[data-src]').forEach(function (img) { - if (img.classList.contains('aksrt-loaded')) return; - if ('IntersectionObserver' in window) { - var obs = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (!entry.isIntersecting) return; - img.src = img.dataset.src; - img.classList.remove('aksrt-lazy'); - img.classList.add('aksrt-loaded'); - obs.unobserve(img); - }); - }, { rootMargin: '200px' }); - obs.observe(img); - } else { - img.src = img.dataset.src; - img.classList.remove('aksrt-lazy'); - img.classList.add('aksrt-loaded'); - } - }); - // Swiper re-init if (typeof Swiper !== 'undefined') { root.querySelectorAll('[data-swiper-config]:not(.swiper-initialized)').forEach(function (el) { diff --git a/assets/js/core/pagination.js b/assets/js/core/pagination.js index 69c910e7..8d8b2b18 100644 --- a/assets/js/core/pagination.js +++ b/assets/js/core/pagination.js @@ -31,9 +31,10 @@ var failed = false; var loadMoreBtn = container.querySelector(".aksrt-load-more-btn, .ajax-next"); var indicator = container.querySelector(".aksrt-loading-indicator"); - var observer = null; - var iasEnabled = false; var successfulAutoLoads = 0; + var iasMax = isComment + ? ((window.aksrtData && typeof aksrtData.commentIasMax !== "undefined") ? parseInt(aksrtData.commentIasMax, 10) : 0) + : ((window.aksrtData && typeof aksrtData.iasMax !== "undefined") ? parseInt(aksrtData.iasMax, 10) : 0); var homepagePanel = container.closest("[data-home-tab-panel]"); var target = homepagePanel || container.previousElementSibling; if (!homepagePanel && target && target.classList && target.classList.contains("content-stream__footer")) { @@ -65,11 +66,6 @@ } function stopInfinite(markNomore) { - if (observer) { - observer.disconnect(); - observer = null; - } - iasEnabled = false; container.classList.remove("ias-pending"); if (markNomore && loadMoreBtn) { loadMoreBtn.textContent = nomoreText; @@ -177,8 +173,6 @@ successfulAutoLoads++; } - if (typeof initLazyLoad === "function") initLazyLoad(target); - if (nextPage > maxPages || !res.data.has_more) { stopInfinite(true); if (!loadMoreBtn) { @@ -198,6 +192,17 @@ if (loadMoreBtn && !loadMoreBtn.classList.contains("ajax-nomore") && !failed) { loadMoreBtn.classList.remove("is-loading"); loadMoreBtn.textContent = triggerText; + if (mode === "infinite") { + if (iasMax > 0 && successfulAutoLoads >= iasMax) { + stopInfinite(true); + } else { + loadMoreBtn.classList.remove("lazyloaded", "lazyloading"); + loadMoreBtn.classList.add("lazyload"); + if (window.lazySizes && lazySizes.loader) { + lazySizes.loader.checkElems(); + } + } + } } }); } @@ -227,127 +232,28 @@ window.location.href = archiveUrl.toString(); return; } + var fromAuto = loadMoreBtn.dataset.lazyAuto === "1"; + delete loadMoreBtn.dataset.lazyAuto; + if (fromAuto && iasMax > 0 && successfulAutoLoads >= iasMax) { + stopInfinite(true); + return; + } // Manual click can recover from a previous auto-load failure. failed = false; - loadNextPage(false); + loadNextPage(fromAuto); }); } - if (mode === "infinite" && "IntersectionObserver" in window) { - if (isComment && window.aksrtData && aksrtData.commentIasAuto === false) { + if (mode === "infinite") { + if ((isComment && window.aksrtData && aksrtData.commentIasAuto === false) || !hasAjaxConfig()) { + loadMoreBtn.classList.remove("lazyload"); + loadMoreBtn.removeAttribute("lazyload-action"); return; } - if (!hasAjaxConfig()) { - return; - } - - var sentinel = document.createElement("div"); - sentinel.className = "aksrt-scroll-sentinel"; - container.appendChild(sentinel); - var lazyloadAction = container.dataset.lazyloadAction || ""; - var iasMax = isComment - ? ((window.aksrtData && typeof aksrtData.commentIasMax !== "undefined") ? parseInt(aksrtData.commentIasMax, 10) : 0) - : ((window.aksrtData && typeof aksrtData.iasMax !== "undefined") ? parseInt(aksrtData.iasMax, 10) : 0); - - observer = new IntersectionObserver( - function (entries) { - if (!iasEnabled || failed) return; - if (!(entries[0] && entries[0].isIntersecting) || loading) return; - if (iasMax > 0 && successfulAutoLoads >= iasMax) { - stopInfinite(true); - return; - } - loadNextPage(true); - }, - { rootMargin: "200px" } - ); - - if (lazyloadAction === "ias") { - container.classList.add("ias-pending"); - } - - // Policy A: do not auto-fire on first paint; wait for real user scroll. - var enableOnScroll = function () { - if (iasEnabled || failed) return; - iasEnabled = true; - container.classList.remove("ias-pending"); - observer.observe(sentinel); - }; - window.addEventListener("scroll", enableOnScroll, { passive: true, once: true }); - window.addEventListener("touchmove", enableOnScroll, { passive: true, once: true }); - window.addEventListener("wheel", enableOnScroll, { passive: true, once: true }); + container.classList.add("ias-pending"); } }); } - function activateLazyEl(el) { - var src = el.dataset.src; - if (!src) return; - var tag = (el.tagName || "").toLowerCase(); - if (tag === "iframe" || tag === "video") { - el.src = src; - el.classList.remove("aksrt-lazy", "lazyload"); - el.classList.add("aksrt-loaded", "lazyloaded"); - return; - } - // img path with optional skeleton + decode - var tempImg = new Image(); - tempImg.onload = function () { - el.src = src; - el.classList.remove("aksrt-lazy", "lazyload"); - el.classList.add("aksrt-loaded", "lazyloaded"); - var skeleton = el.previousElementSibling; - if (skeleton && skeleton.classList.contains("aksrt-skeleton")) { - skeleton.remove(); - } - }; - tempImg.onerror = function () { - el.classList.remove("aksrt-lazy", "lazyload"); - el.classList.add("aksrt-error"); - var skeleton = el.previousElementSibling; - if (skeleton && skeleton.classList.contains("aksrt-skeleton")) { - skeleton.remove(); - } - }; - tempImg.src = src; - } - - function initLazyLoad(root) { - var scope = root || document; - // Images + iframes/videos that use data-src (zibll lazyload / aksrt-lazy). - var lazyEls = scope.querySelectorAll( - "img.aksrt-lazy, iframe.aksrt-lazy, iframe.lazyload, video.aksrt-lazy, video.lazyload" - ); - if (!lazyEls.length) return; - - if (!("IntersectionObserver" in window)) { - lazyEls.forEach(function (el) { - activateLazyEl(el); - }); - return; - } - - var observer = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (!entry.isIntersecting) return; - var el = entry.target; - if (!el.dataset.src) { - observer.unobserve(el); - return; - } - activateLazyEl(el); - observer.unobserve(el); - }); - }, { - rootMargin: "200px 0px", - threshold: 0.01 - }); - - lazyEls.forEach(function (el) { - observer.observe(el); - }); - } - window.AksrtFrontend.register("aksrt-ajax-pagination", function (root) { initAjaxPagination(root); }); - window.AksrtFrontend.register("aksrt-lazy-load", function (root) { initLazyLoad(root); }); })(); diff --git a/assets/js/core/slider.js b/assets/js/core/slider.js index 79f9cf03..a05c40b8 100644 --- a/assets/js/core/slider.js +++ b/assets/js/core/slider.js @@ -191,7 +191,6 @@ spaceBetween: spaceBetween, parallax: parallax, effect: effect, - lazy: { loadPrevNext: true }, autoplay: autoplay, speed: speed, pagination: pagination, @@ -199,9 +198,6 @@ keyboard: keyboard, a11y: { prevSlideMessage: "上一张", nextSlideMessage: "下一张" }, on: { - lazyImageReady: function (slideEl, imageEl) { - if (imageEl) imageEl.classList.add("lazyloaded"); - }, slideChange: function () { el.querySelectorAll(".aksrt-dplayer, video").forEach(function (media) { if (media.pause) media.pause(); @@ -238,7 +234,7 @@ var img = slide.querySelector("img"); var src = img ? (img.dataset.src || img.src) : ""; if (src) { - return '
'; + return '
'; } return ""; }; @@ -379,4 +375,4 @@ } window.AksrtFrontend.register("aksrt-hero-slider", function () { initHeroSlider(); }); -})(); \ No newline at end of file +})(); diff --git a/assets/js/credits.js b/assets/js/credits.js index 7b8739f3..b31d4c72 100644 --- a/assets/js/credits.js +++ b/assets/js/credits.js @@ -597,7 +597,7 @@ var item = document.createElement('button'); item.type = 'button'; item.className = 'uc-transfer-user'; - item.innerHTML = '' + (u.display_name || u.user_login) + '@' + (u.user_login || '') + ''; + item.innerHTML = '' + (u.display_name || u.user_login) + '@' + (u.user_login || '') + ''; item.addEventListener('click', function () { if (recipientInput) recipientInput.value = String(u.id); if (selected) selected.textContent = '已选择:' + (u.display_name || u.user_login); diff --git a/assets/js/enhancements.js b/assets/js/enhancements.js index f12d6c17..e9cd4f4e 100644 --- a/assets/js/enhancements.js +++ b/assets/js/enhancements.js @@ -106,7 +106,7 @@ 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 EXCLUDE_SEL = '.no-imgbox,.no-lightbox,.avatar,.img-icon,.avatar-badge,.smilie-icon,.aksrt-avatar'; 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; diff --git a/assets/js/header-inline-search.js b/assets/js/header-inline-search.js index 9effc6d7..f34f6103 100644 --- a/assets/js/header-inline-search.js +++ b/assets/js/header-inline-search.js @@ -276,32 +276,9 @@ /** Generic remote-box / lazyload-action=ias (zibll-compatible). */ function bindRemoteBoxes(root) { (root || document).querySelectorAll("[remote-box]").forEach(function (box) { - var form = box.closest("form"); - if (form) { - form.addEventListener( - "mouseenter", - function () { - loadRemote(box); - }, - { once: true } - ); - form.addEventListener( - "focusin", - function () { - loadRemote(box); - }, - { once: true } - ); - } - if ("requestIdleCallback" in window) { - requestIdleCallback(function () { - loadRemote(box); - }); - } else { - setTimeout(function () { - loadRemote(box); - }, 600); - } + if (box.dataset.aksrtRemoteBound === "1") return; + box.dataset.aksrtRemoteBound = "1"; + box.addEventListener("click", function () { loadRemote(box); }); }); } diff --git a/assets/js/homepage-fixed.js b/assets/js/homepage-fixed.js index e7fc03cb..29c483c8 100644 --- a/assets/js/homepage-fixed.js +++ b/assets/js/homepage-fixed.js @@ -226,53 +226,6 @@ return wrapper; } - function hydrateLazyImages(scope) { - var lazyImages = (scope || document).querySelectorAll('img.aksrt-lazy'); - if (!lazyImages.length) return; - - if (!('IntersectionObserver' in window)) { - lazyImages.forEach(function (img) { - var src = img.dataset.src; - if (src) { - img.src = src; - img.classList.remove('aksrt-lazy'); - img.classList.add('aksrt-loaded'); - } - var skeleton = img.previousElementSibling; - if (skeleton && skeleton.classList.contains('aksrt-skeleton')) skeleton.remove(); - }); - return; - } - - var observer = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - if (!entry.isIntersecting) return; - var img = entry.target; - var src = img.dataset.src; - if (!src) { observer.unobserve(img); return; } - - var tempImg = new Image(); - tempImg.onload = function () { - img.src = src; - img.classList.remove('aksrt-lazy'); - img.classList.add('aksrt-loaded'); - var skeleton = img.previousElementSibling; - if (skeleton && skeleton.classList.contains('aksrt-skeleton')) skeleton.remove(); - }; - tempImg.onerror = function () { - img.classList.remove('aksrt-lazy'); - img.classList.add('aksrt-error'); - var skeleton = img.previousElementSibling; - if (skeleton && skeleton.classList.contains('aksrt-skeleton')) skeleton.remove(); - }; - tempImg.src = src; - observer.unobserve(img); - }); - }, { rootMargin: '200px 0px', threshold: 0.01 }); - - lazyImages.forEach(function (img) { observer.observe(img); }); - } - /** * Hydrate a panel after AJAX content injection. * Sets data-view, re-binds lazy images, and dispatches events. @@ -285,7 +238,6 @@ postsContainer.setAttribute('data-view', viewMode || getDefaultView()); } - hydrateLazyImages(panel); dispatchRenderedEvent(panel, { viewMode: viewMode || getDefaultView(), panel: panel.getAttribute('data-home-tab-panel') || '' diff --git a/assets/js/lazysizes.js b/assets/js/lazysizes.js new file mode 100644 index 00000000..9780c74d --- /dev/null +++ b/assets/js/lazysizes.js @@ -0,0 +1,870 @@ +//lazySizes +(function (window, factory) { + var lazySizes = factory(window, window.document, Date); + window.lazySizes = lazySizes; + if (typeof module == 'object' && module.exports) { + module.exports = lazySizes; + } +})(typeof window != 'undefined' ? window : {}, function l(window, document, Date) { + // Pass in the windoe Date function also for SSR because the Date class can be lost + 'use strict'; + /*jshint eqnull:true */ + + var lazysizes, lazySizesCfg; + + (function () { + var prop; + + var lazySizesDefaults = { + lazyClass: 'lazyload', + loadedClass: 'lazyloaded', + loadingClass: 'lazyloading', + preloadClass: 'lazypreload', + loadafterClass: 'lazyloadafter', + errorClass: 'lazyerror', + //strictClass: 'lazystrict', + autosizesClass: 'lazyautosizes', + srcAttr: 'data-src', + srcsetAttr: 'data-srcset', + sizesAttr: 'data-sizes', + //preloadAfterLoad: false, + minSize: 40, + customMedia: {}, + init: true, + expFactor: 1.5, + hFac: 0.8, + loadMode: 2, + loadHidden: true, + ricTimeout: 0, + throttleDelay: 125, + }; + + lazySizesCfg = window.lazySizesConfig || window.lazysizesConfig || {}; + + for (prop in lazySizesDefaults) { + if (!(prop in lazySizesCfg)) { + lazySizesCfg[prop] = lazySizesDefaults[prop]; + } + } + })(); + + if (!document || !document.getElementsByClassName) { + return { + init: function () {}, + cfg: lazySizesCfg, + noSupport: true, + }; + } + + var docElem = document.documentElement; + + var supportPicture = window.HTMLPictureElement; + + var _addEventListener = 'addEventListener'; + + var _getAttribute = 'getAttribute'; + + /** + * Update to bind to window because 'this' becomes null during SSR + * builds. + */ + var addEventListener = window[_addEventListener].bind(window); + + var setTimeout = window.setTimeout; + + var requestAnimationFrame = window.requestAnimationFrame || setTimeout; + + var requestIdleCallback = window.requestIdleCallback; + + var regPicture = /^picture$/i; + + var loadEvents = ['load', 'error', 'lazyincluded', '_lazyloaded']; + + var regClassCache = {}; + + var forEach = Array.prototype.forEach; + + var hasClass = function (ele, cls) { + if (!regClassCache[cls]) { + regClassCache[cls] = new RegExp('(\\s|^)' + cls + '(\\s|$)'); + } + return regClassCache[cls].test(ele[_getAttribute]('class') || '') && regClassCache[cls]; + }; + + var addClass = function (ele, cls) { + if (!hasClass(ele, cls)) { + ele.setAttribute('class', (ele[_getAttribute]('class') || '').trim() + ' ' + cls); + } + }; + + var removeClass = function (ele, cls) { + var reg; + if ((reg = hasClass(ele, cls))) { + ele.setAttribute('class', (ele[_getAttribute]('class') || '').replace(reg, ' ')); + } + }; + + var addRemoveLoadEvents = function (dom, fn, add) { + var action = add ? _addEventListener : 'removeEventListener'; + if (add) { + addRemoveLoadEvents(dom, fn); + } + loadEvents.forEach(function (evt) { + dom[action](evt, fn); + }); + }; + + var triggerEvent = function (elem, name, detail, noBubbles, noCancelable) { + var event = document.createEvent('Event'); + + if (!detail) { + detail = {}; + } + + detail.instance = lazysizes; + + event.initEvent(name, !noBubbles, !noCancelable); + + event.detail = detail; + + elem.dispatchEvent(event); + return event; + }; + + var updatePolyfill = function (el, full) { + var polyfill; + if (!supportPicture && (polyfill = window.picturefill || lazySizesCfg.pf)) { + if (full && full.src && !el[_getAttribute]('srcset')) { + el.setAttribute('srcset', full.src); + } + polyfill({ reevaluate: true, elements: [el] }); + } else if (full && full.src) { + el.src = full.src; + } + }; + + var getCSS = function (elem, style) { + return (getComputedStyle(elem, null) || {})[style]; + }; + + var getWidth = function (elem, parent, width) { + width = width || elem.offsetWidth; + + while (width < lazySizesCfg.minSize && parent && !elem._lazysizesWidth) { + width = parent.offsetWidth; + parent = parent.parentNode; + } + + return width; + }; + + var rAF = (function () { + var running, waiting; + var firstFns = []; + var secondFns = []; + var fns = firstFns; + + var run = function () { + var runFns = fns; + + fns = firstFns.length ? secondFns : firstFns; + + running = true; + waiting = false; + + while (runFns.length) { + runFns.shift()(); + } + + running = false; + }; + + var rafBatch = function (fn, queue) { + if (running && !queue) { + fn.apply(this, arguments); + } else { + fns.push(fn); + + if (!waiting) { + waiting = true; + (document.hidden ? setTimeout : requestAnimationFrame)(run); + } + } + }; + + rafBatch._lsFlush = run; + + return rafBatch; + })(); + + var rAFIt = function (fn, simple) { + return simple + ? function () { + rAF(fn); + } + : function () { + var that = this; + var args = arguments; + rAF(function () { + fn.apply(that, args); + }); + }; + }; + + var throttle = function (fn) { + var running; + var lastTime = 0; + var gDelay = lazySizesCfg.throttleDelay; + var rICTimeout = lazySizesCfg.ricTimeout; + var run = function () { + running = false; + lastTime = Date.now(); + fn(); + }; + var idleCallback = + requestIdleCallback && rICTimeout > 49 + ? function () { + requestIdleCallback(run, { timeout: rICTimeout }); + + if (rICTimeout !== lazySizesCfg.ricTimeout) { + rICTimeout = lazySizesCfg.ricTimeout; + } + } + : rAFIt(function () { + setTimeout(run); + }, true); + return function (isPriority) { + var delay; + + if ((isPriority = isPriority === true)) { + rICTimeout = 33; + } + + if (running) { + return; + } + + running = true; + + delay = gDelay - (Date.now() - lastTime); + + if (delay < 0) { + delay = 0; + } + + if (isPriority || delay < 9) { + idleCallback(); + } else { + setTimeout(idleCallback, delay); + } + }; + }; + + //based on http://modernjavascript.blogspot.de/2013/08/building-better-debounce.html + var debounce = function (func) { + var timeout, timestamp; + var wait = 99; + var run = function () { + timeout = null; + func(); + }; + var later = function () { + var last = Date.now() - timestamp; + + if (last < wait) { + setTimeout(later, wait - last); + } else { + (requestIdleCallback || run)(run); + } + }; + + return function () { + timestamp = Date.now(); + + if (!timeout) { + timeout = setTimeout(later, wait); + } + }; + }; + + var loader = (function () { + var preloadElems, isCompleted, resetPreloadingTimer, loadMode, started; + + var eLvW, elvH, eLtop, eLleft, eLright, eLbottom, isBodyHidden; + + var regImg = /^img$/i; + var regIframe = /^iframe$/i; + + var supportScroll = 'onscroll' in window && !/(gle|ing)bot/.test(navigator.userAgent); + + var shrinkExpand = 0; + var currentExpand = 0; + + var isLoading = 0; + var lowRuns = -1; + + var resetPreloading = function (e) { + isLoading--; + if (!e || isLoading < 0 || !e.target) { + isLoading = 0; + } + }; + + var isVisible = function (elem) { + if (isBodyHidden == null) { + isBodyHidden = getCSS(document.body, 'visibility') == 'hidden'; + } + + return isBodyHidden || !(getCSS(elem.parentNode, 'visibility') == 'hidden' && getCSS(elem, 'visibility') == 'hidden'); + }; + + var isNestedVisible = function (elem, elemExpand) { + var outerRect; + var parent = elem; + var visible = isVisible(elem); + + eLtop -= elemExpand; + eLbottom += elemExpand; + eLleft -= elemExpand; + eLright += elemExpand; + + while (visible && (parent = parent.offsetParent) && parent != document.body && parent != docElem) { + visible = (getCSS(parent, 'opacity') || 1) > 0; + + if (visible && getCSS(parent, 'overflow') != 'visible') { + outerRect = parent.getBoundingClientRect(); + visible = eLright > outerRect.left && eLleft < outerRect.right && eLbottom > outerRect.top - 1 && eLtop < outerRect.bottom + 1; + } + } + + return visible; + }; + + var checkElements = function () { + var eLlen, i, rect, autoLoadElem, loadedSomething, elemExpand, elemNegativeExpand, elemExpandVal, beforeExpandVal, defaultExpand, preloadExpand, hFac; + var lazyloadElems = lazysizes.elements; + + if ((loadMode = lazySizesCfg.loadMode) && isLoading < 8 && (eLlen = lazyloadElems.length)) { + i = 0; + + lowRuns++; + + for (; i < eLlen; i++) { + if (!lazyloadElems[i] || lazyloadElems[i]._lazyRace) { + continue; + } + + if (!supportScroll || (lazysizes.prematureUnveil && lazysizes.prematureUnveil(lazyloadElems[i]))) { + unveilElement(lazyloadElems[i]); + continue; + } + + if (!(elemExpandVal = lazyloadElems[i][_getAttribute]('data-expand')) || !(elemExpand = elemExpandVal * 1)) { + elemExpand = currentExpand; + } + + if (!defaultExpand) { + defaultExpand = !lazySizesCfg.expand || lazySizesCfg.expand < 1 ? (docElem.clientHeight > 500 && docElem.clientWidth > 500 ? 500 : 370) : lazySizesCfg.expand; + + lazysizes._defEx = defaultExpand; + + preloadExpand = defaultExpand * lazySizesCfg.expFactor; + hFac = lazySizesCfg.hFac; + isBodyHidden = null; + + if (currentExpand < preloadExpand && isLoading < 1 && lowRuns > 2 && loadMode > 2 && !document.hidden) { + currentExpand = preloadExpand; + lowRuns = 0; + } else if (loadMode > 1 && lowRuns > 1 && isLoading < 6) { + currentExpand = defaultExpand; + } else { + currentExpand = shrinkExpand; + } + } + + if (beforeExpandVal !== elemExpand) { + eLvW = innerWidth + elemExpand * hFac; + elvH = innerHeight + elemExpand; + elemNegativeExpand = elemExpand * -1; + beforeExpandVal = elemExpand; + } + + rect = lazyloadElems[i].getBoundingClientRect(); + + if ((eLbottom = rect.bottom) >= elemNegativeExpand && (eLtop = rect.top) <= elvH && (eLright = rect.right) >= elemNegativeExpand * hFac && (eLleft = rect.left) <= eLvW && (eLbottom || eLright || eLleft || eLtop) && (lazySizesCfg.loadHidden || isVisible(lazyloadElems[i])) && ((isCompleted && isLoading < 3 && !elemExpandVal && (loadMode < 3 || lowRuns < 4)) || isNestedVisible(lazyloadElems[i], elemExpand))) { + unveilElement(lazyloadElems[i]); + loadedSomething = true; + if (isLoading > 9) { + break; + } + } else if (!loadedSomething && isCompleted && !autoLoadElem && isLoading < 4 && lowRuns < 4 && loadMode > 2 && (preloadElems[0] || lazySizesCfg.preloadAfterLoad) && (preloadElems[0] || (!elemExpandVal && (eLbottom || eLright || eLleft || eLtop || lazyloadElems[i][_getAttribute](lazySizesCfg.sizesAttr) != 'auto')))) { + autoLoadElem = preloadElems[0] || lazyloadElems[i]; + } + } + + if (autoLoadElem && !loadedSomething) { + unveilElement(autoLoadElem); + } + } + }; + + var throttledCheckElements = throttle(checkElements); + + var switchLoadingClass = function (e) { + var elem = e.target; + + if (elem._lazyCache) { + delete elem._lazyCache; + return; + } + + resetPreloading(e); + addClass(elem, lazySizesCfg.loadedClass); + removeClass(elem, lazySizesCfg.loadingClass); + addRemoveLoadEvents(elem, rafSwitchLoadingClass); + triggerEvent(elem, 'lazyloaded'); + + //添加loadafterClass,防止后期二次触发动画 + setTimeout(function () { + addClass(elem, lazySizesCfg.loadafterClass); + }, 700); + }; + var rafedSwitchLoadingClass = rAFIt(switchLoadingClass); + var rafSwitchLoadingClass = function (e) { + rafedSwitchLoadingClass({ target: e.target }); + }; + + var changeIframeSrc = function (elem, src) { + try { + elem.contentWindow.location.replace(src); + } catch (e) { + elem.src = src; + } + }; + + var handleSources = function (source) { + var customMedia; + + var sourceSrcset = source[_getAttribute](lazySizesCfg.srcsetAttr); + + if ((customMedia = lazySizesCfg.customMedia[source[_getAttribute]('data-media') || source[_getAttribute]('media')])) { + source.setAttribute('media', customMedia); + } + + if (sourceSrcset) { + source.setAttribute('srcset', sourceSrcset); + } + }; + + var lazyUnveil = rAFIt(function (elem, detail, isAuto, sizes, isImg) { + var src, srcset, parent, isPicture, event, firesLoad; + + if (!(event = triggerEvent(elem, 'lazybeforeunveil', detail)).defaultPrevented) { + if (sizes) { + if (isAuto) { + addClass(elem, lazySizesCfg.autosizesClass); + } else { + elem.setAttribute('sizes', sizes); + } + } + + srcset = elem[_getAttribute](lazySizesCfg.srcsetAttr); + src = elem[_getAttribute](lazySizesCfg.srcAttr); + + if (isImg) { + parent = elem.parentNode; + isPicture = parent && regPicture.test(parent.nodeName || ''); + } + + firesLoad = detail.firesLoad || ('src' in elem && (srcset || src || isPicture)); + + event = { target: elem }; + + addClass(elem, lazySizesCfg.loadingClass); + + if (firesLoad) { + clearTimeout(resetPreloadingTimer); + resetPreloadingTimer = setTimeout(resetPreloading, 2500); + addRemoveLoadEvents(elem, rafSwitchLoadingClass, true); + } + + if (isPicture) { + forEach.call(parent.getElementsByTagName('source'), handleSources); + } + + if (srcset) { + elem.setAttribute('srcset', srcset); + } else if (src && !isPicture) { + if (regIframe.test(elem.nodeName)) { + changeIframeSrc(elem, src); + } else { + elem.src = src; + } + } + + if (isImg && (srcset || isPicture)) { + updatePolyfill(elem, { src: src }); + } + } + + if (elem._lazyRace) { + delete elem._lazyRace; + } + removeClass(elem, lazySizesCfg.lazyClass); + + rAF(function () { + // Part of this can be removed as soon as this fix is older: https://bugs.chromium.org/p/chromium/issues/detail?id=7731 (2015) + var isLoaded = elem.complete && elem.naturalWidth > 1; + + if (!firesLoad || isLoaded) { + if (isLoaded) { + addClass(elem, 'ls-is-cached'); + } + switchLoadingClass(event); + elem._lazyCache = true; + setTimeout(function () { + if ('_lazyCache' in elem) { + delete elem._lazyCache; + } + }, 9); + } + if (elem.loading == 'lazy') { + isLoading--; + } + }, true); + }); + + var unveilElement = function (elem) { + if (elem._lazyRace) { + return; + } + var detail; + + var isImg = regImg.test(elem.nodeName); + + //allow using sizes="auto", but don't use. it's invalid. Use data-sizes="auto" or a valid value for sizes instead (i.e.: sizes="80vw") + var sizes = isImg && (elem[_getAttribute](lazySizesCfg.sizesAttr) || elem[_getAttribute]('sizes')); + var isAuto = sizes == 'auto'; + + if ((isAuto || !isCompleted) && isImg && (elem[_getAttribute]('src') || elem.srcset) && !elem.complete && !hasClass(elem, lazySizesCfg.errorClass) && hasClass(elem, lazySizesCfg.lazyClass)) { + return; + } + + detail = triggerEvent(elem, 'lazyunveilread').detail; + + if (isAuto) { + autoSizer.updateElem(elem, true, elem.offsetWidth); + } + + elem._lazyRace = true; + isLoading++; + + lazyUnveil(elem, detail, isAuto, sizes, isImg); + }; + + var afterScroll = debounce(function () { + lazySizesCfg.loadMode = 3; + throttledCheckElements(); + }); + + var altLoadmodeScrollListner = function () { + if (lazySizesCfg.loadMode == 3) { + lazySizesCfg.loadMode = 2; + } + afterScroll(); + }; + + var onload = function () { + if (isCompleted) { + return; + } + if (Date.now() - started < 999) { + setTimeout(onload, 999); + return; + } + + isCompleted = true; + + lazySizesCfg.loadMode = 3; + + throttledCheckElements(); + + addEventListener('scroll', altLoadmodeScrollListner, true); + }; + + return { + _: function () { + started = Date.now(); + + lazysizes.elements = document.getElementsByClassName(lazySizesCfg.lazyClass); + preloadElems = document.getElementsByClassName(lazySizesCfg.lazyClass + ' ' + lazySizesCfg.preloadClass); + + addEventListener('scroll', throttledCheckElements, true); + + addEventListener('resize', throttledCheckElements, true); + + addEventListener('pageshow', function (e) { + if (e.persisted) { + var loadingElements = document.querySelectorAll('.' + lazySizesCfg.loadingClass); + + if (loadingElements.length && loadingElements.forEach) { + requestAnimationFrame(function () { + loadingElements.forEach(function (img) { + if (img.complete) { + unveilElement(img); + } + }); + }); + } + } + }); + + if (window.MutationObserver) { + new MutationObserver(throttledCheckElements).observe(docElem, { childList: true, subtree: true, attributes: true }); + } else { + docElem[_addEventListener]('DOMNodeInserted', throttledCheckElements, true); + docElem[_addEventListener]('DOMAttrModified', throttledCheckElements, true); + setInterval(throttledCheckElements, 999); + } + + addEventListener('hashchange', throttledCheckElements, true); + + //, 'fullscreenchange' + ['focus', 'mouseover', 'click', 'load', 'transitionend', 'animationend'].forEach(function (name) { + document[_addEventListener](name, throttledCheckElements, true); + }); + + if (/d$|^c/.test(document.readyState)) { + onload(); + } else { + addEventListener('load', onload); + document[_addEventListener]('DOMContentLoaded', throttledCheckElements); + setTimeout(onload, 20000); + } + + if (lazysizes.elements.length) { + checkElements(); + rAF._lsFlush(); + } else { + throttledCheckElements(); + } + }, + checkElems: throttledCheckElements, + unveil: unveilElement, + _aLSL: altLoadmodeScrollListner, + }; + })(); + + var autoSizer = (function () { + var autosizesElems; + + var sizeElement = rAFIt(function (elem, parent, event, width) { + var sources, i, len; + elem._lazysizesWidth = width; + width += 'px'; + + elem.setAttribute('sizes', width); + + if (regPicture.test(parent.nodeName || '')) { + sources = parent.getElementsByTagName('source'); + for (i = 0, len = sources.length; i < len; i++) { + sources[i].setAttribute('sizes', width); + } + } + + if (!event.detail.dataAttr) { + updatePolyfill(elem, event.detail); + } + }); + var getSizeElement = function (elem, dataAttr, width) { + var event; + var parent = elem.parentNode; + + if (parent) { + width = getWidth(elem, parent, width); + event = triggerEvent(elem, 'lazybeforesizes', { width: width, dataAttr: !!dataAttr }); + + if (!event.defaultPrevented) { + width = event.detail.width; + + if (width && width !== elem._lazysizesWidth) { + sizeElement(elem, parent, event, width); + } + } + } + }; + + var updateElementsSizes = function () { + var i; + var len = autosizesElems.length; + if (len) { + i = 0; + + for (; i < len; i++) { + getSizeElement(autosizesElems[i]); + } + } + }; + + var debouncedUpdateElementsSizes = debounce(updateElementsSizes); + + return { + _: function () { + autosizesElems = document.getElementsByClassName(lazySizesCfg.autosizesClass); + addEventListener('resize', debouncedUpdateElementsSizes); + }, + checkElems: debouncedUpdateElementsSizes, + updateElem: getSizeElement, + }; + })(); + + var init = function () { + if (!init.i && document.getElementsByClassName) { + init.i = true; + autoSizer._(); + loader._(); + } + }; + + setTimeout(function () { + if (lazySizesCfg.init) { + init(); + } + }); + + lazysizes = { + cfg: lazySizesCfg, + autoSizer: autoSizer, + loader: loader, + init: init, + uP: updatePolyfill, + aC: addClass, + rC: removeClass, + hC: hasClass, + fire: triggerEvent, + gW: getWidth, + rAF: rAF, + }; + + return lazysizes; +}); + + +/** + * AKSRT integration for Zibll's lazySizes runtime. + * Normalizes dynamic markup without creating a second media loader. + */ +(function (window, document) { + 'use strict'; + + function normalize(root) { + var scope = root && root.querySelectorAll ? root : document; + scope.querySelectorAll('img,iframe,video,[data-bg],[lazyload-action]').forEach(function (element) { + var hasLazySource = element.hasAttribute('data-src') || element.hasAttribute('data-srcset'); + if (element.tagName === 'VIDEO' && element.querySelector('source[data-src],source[data-srcset]')) { + hasLazySource = true; + } + if ((hasLazySource || element.hasAttribute('data-bg') || element.hasAttribute('lazyload-action')) && + !element.classList.contains('lazyloaded') && !element.classList.contains('lazyloading')) { + element.classList.add('lazyload'); + } + }); + } + + function escapeAttribute(value) { + return String(value == null ? '' : value) + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(//g, '>'); + } + + window.aksrtGetLazyImageAttr = function (key, src, className, size) { + var config = window.aksrtData || {}; + var scopes = config.lazyScopes || {}; + var classes = String(className || '').trim(); + var realSrc = escapeAttribute(src); + if (!src || scopes[key] === false) { + return ' class="' + escapeAttribute(classes) + '" src="' + realSrc + '"'; + } + var thumbs = config.lazyThumbs || {}; + var placeholder = key === 'lazy_avatar' + ? (config.lazyAvatar || thumbs.sm || '') + : (thumbs[size || 'default'] || thumbs.default || ''); + return ' class="lazyload ' + escapeAttribute(classes) + '" src="' + escapeAttribute(placeholder) + '" data-src="' + realSrc + '"'; + }; + + document.addEventListener('lazybeforeunveil', function (event) { + var element = event.target; + var action = element.getAttribute('lazyload-action'); + + window.setTimeout(function () { + if (action === 'ias') { + if (element.hasAttribute('remote-box')) { + element.click(); + } else { + var trigger = element.matches('.ajax-next,.ias-btn') + ? element + : element.querySelector('.ajax-next,.ias-btn'); + if (trigger) { + trigger.setAttribute('no-scroll', 'true'); + trigger.dataset.lazyAuto = '1'; + trigger.click(); + } + } + } else if (action === 'animated') { + var animation = element.getAttribute('data-animated'); + if (animation) { + element.classList.add('animated', animation); + element.style.visibility = ''; + } + } + }, 200); + + var background = element.getAttribute('data-bg'); + if (background) { + element.style.backgroundImage = 'url("' + background.replace(/"/g, '\\"') + '")'; + } + + if (element.tagName === 'VIDEO') { + var changed = false; + var poster = element.getAttribute('data-poster'); + if (poster) { + element.setAttribute('poster', poster); + } + element.querySelectorAll('source[data-src],source[data-srcset]').forEach(function (source) { + var src = source.getAttribute('data-src'); + var srcset = source.getAttribute('data-srcset'); + if (src) { + source.setAttribute('src', src); + changed = true; + } + if (srcset) { + source.setAttribute('srcset', srcset); + changed = true; + } + }); + if (changed) { + event.detail.firesLoad = true; + element.load(); + } + } + }); + + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', function () { normalize(document); }); + } else { + normalize(document); + } + + new MutationObserver(function (records) { + records.forEach(function (record) { + record.addedNodes.forEach(function (node) { + if (node.nodeType !== 1) return; + normalize(node); + }); + }); + }).observe(document.documentElement, { childList: true, subtree: true }); + + window.aksrtNormalizeLazyMedia = normalize; +})(window, document); diff --git a/assets/js/order-center.js b/assets/js/order-center.js index 3bc55d02..343c80c3 100644 --- a/assets/js/order-center.js +++ b/assets/js/order-center.js @@ -81,7 +81,7 @@ html += '' + formatMoney(order) + ""; html += '
'; html += image - ? '' + ? '' : ''; html += '
' + escapeHtml(title) + ""; html += '' + escapeHtml(order.product_type_label || "") + "
"; @@ -174,7 +174,7 @@ function cardHtml(order) { var image = order.image - ? '' + ? '' : ''; var countdown = Number(order.time_left || 0) > 0 ? '' diff --git a/assets/js/page-navs.js b/assets/js/page-navs.js index b4a25d20..26ae5a3e 100644 --- a/assets/js/page-navs.js +++ b/assets/js/page-navs.js @@ -288,7 +288,7 @@ items.forEach(function (item) { html += ''; if (item.thumbnail) { - html += ''; + html += ''; } html += '
'; html += '

' + this.escHtml(item.title) + '

'; diff --git a/assets/js/pay-vip.js b/assets/js/pay-vip.js index 069ca132..662220b0 100644 --- a/assets/js/pay-vip.js +++ b/assets/js/pay-vip.js @@ -17,6 +17,12 @@ return el; } + function lazyImage(key, src, className, size) { + var template = document.createElement("template"); + template.innerHTML = "'; + return template.content.firstElementChild; + } + function append(parent, child) { if (child) parent.appendChild(child); return child; @@ -299,11 +305,7 @@ var levelInfo = levels[String(level)] || {}; var levelHeader = node("div", "aksrt-vip-level-header"); if (levelInfo.img_icon) { - var levelIcon = node("img", "aksrt-vip-level-icon"); - levelIcon.src = text(levelInfo.img_icon); - levelIcon.alt = ""; - levelIcon.loading = "lazy"; - levelIcon.decoding = "async"; + var levelIcon = lazyImage("lazy_other", text(levelInfo.img_icon), "aksrt-vip-level-icon"); append(levelHeader, levelIcon); } append(levelHeader, node("div", "v-name font-bold", group.name || levelInfo.name || "VIP" + level)); diff --git a/assets/js/user-badges.js b/assets/js/user-badges.js index 98f4d4e0..93a299a7 100644 --- a/assets/js/user-badges.js +++ b/assets/js/user-badges.js @@ -45,7 +45,7 @@ var icon = item && item.icon_url ? String(item.icon_url) : ''; var label = item && item.label ? String(item.label) : ''; if (icon) { - return '' + escapeHtml(label) + ''; + return ''; } return ''; } @@ -131,7 +131,7 @@ return '
' + '
' + - '
' + + '
' + iconHtml(item) + '
' + escapeHtml(item.label) + '
' + (item.desc ? '
' + escapeHtml(item.desc) + '
' : '') + stateLine + '
' + (action ? '
' + action + '
' : '') + diff --git a/assets/widgets/frontend.js b/assets/widgets/frontend.js index f84dcd4e..96e0b4a7 100644 --- a/assets/widgets/frontend.js +++ b/assets/widgets/frontend.js @@ -118,24 +118,32 @@ if (message) message.hidden = false; }); } - if (typeof window.DPlayer !== 'function') return; - 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(); + 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(); } } @@ -254,15 +262,6 @@ if (more) loadItems(container, Number(more.dataset.nextPage || 2), true); if (pageButton) loadItems(container, Number(pageButton.dataset.widgetPage || 1), false); }); - if ('IntersectionObserver' in window) { - var observer = new IntersectionObserver(function (entries) { - entries.forEach(function (entry) { - var more = container.querySelector('.aksrt-widget-load-more[data-mode="infinite"]'); - if (entry.isIntersecting && more) loadItems(container, Number(more.dataset.nextPage || 2), true); - }); - }, { rootMargin: '240px' }); - observer.observe(container); - } } function initTabs(tabs) { diff --git a/footer.php b/footer.php index 2dd0f78e..b9edc80a 100644 --- a/footer.php +++ b/footer.php @@ -299,7 +299,7 @@ if ($_footer_conter === '') { ?>