370 lines
13 KiB
JavaScript
Executable File
370 lines
13 KiB
JavaScript
Executable File
/**
|
||
* fInitArticleLoader - 插件版
|
||
*/
|
||
/**
|
||
* articles.js
|
||
*/
|
||
//export const fInitArticleLoader = () => {
|
||
// // 直接用全局 $, 别 import
|
||
// const $oContainer = $('.list-home');
|
||
// const sSlug = $('meta[name="article-slug"]').attr('content') || "freenode";
|
||
//
|
||
// if (!$oContainer.length) return;
|
||
//
|
||
// // 插件初始化
|
||
// $oContainer.infiniteScroll({
|
||
// path: `/${sSlug}?page={{#}}`,
|
||
// append: '.ajax-item',
|
||
// history: false,
|
||
// prefill: true, // 解决大屏不加载的救星
|
||
// status: '.page-load-status',
|
||
// scrollThreshold: 400,
|
||
// responseBody: 'text' // 后端返回 HTML 片段
|
||
// });
|
||
//
|
||
// console.log('[ArticleLoader] 监听已启动');
|
||
//};
|
||
|
||
/**
|
||
* fInitArticleLoader - 使用成熟插件的艺术级实现
|
||
*/
|
||
//export const fInitArticleLoader = () => {
|
||
// const $oContainer = $('.list-home');
|
||
// const sSlug = $('meta[name="article-slug"]').attr('content') || "freenode";
|
||
//
|
||
// if (!$oContainer.length) return;
|
||
//
|
||
// // 先销毁可能存在的旧实例,防止重复绑定没反应
|
||
// if ($oContainer.data('infiniteScroll')) {
|
||
// $oContainer.infiniteScroll('destroy');
|
||
// }
|
||
//
|
||
// $oContainer.infiniteScroll({
|
||
// // 自动拼接分页 URL:/news?page=2
|
||
// path: `/${sSlug}?page={{#}}`,
|
||
//
|
||
// // 关键:插件会从加载的页面里找这个类名的元素追加进来
|
||
// append: '.ajax-item',
|
||
//
|
||
// // 关键:如果后端返回的是 JSON 里的 HTML 字符串,必须开启响应类型
|
||
// // 但通常我们建议后端直接返回渲染好的 HTML 片段,最省心
|
||
// responseBody: 'text',
|
||
//
|
||
// history: false,
|
||
// prefill: true, // 大屏救星,自动填满
|
||
// status: '.page-load-status',
|
||
// scrollThreshold: 400
|
||
// });
|
||
//
|
||
// // 调试暗号:看看插件到底动没动
|
||
// $oContainer.on('request.infiniteScroll', () => console.log('正在请求新货...'));
|
||
// $oContainer.on('load.infiniteScroll', () => console.log('请求成功,正在抠图...'));
|
||
// $oContainer.on('append.infiniteScroll', () => console.log('艺术品已挂墙!'));
|
||
// $oContainer.on('error.infiniteScroll', (oEv, oErr) => console.error('坏了:', oErr));
|
||
//};
|
||
|
||
/**
|
||
* fInitArticleLoader - 究极稳定版(解决大屏不触发顽疾)
|
||
*/
|
||
/**
|
||
* fInitArticleLoader - 终极防御版
|
||
* 解决大屏不触发、Observer不更新状态的终极方案
|
||
*/
|
||
/**
|
||
* fInitArticleLoader - 艺术级无限滚动加载器 (大屏适配 + 精准位置版)
|
||
* s - string / i - integer / b - boolean / o - object / x - unknown
|
||
*/
|
||
//export const fInitArticleLoader = () => {
|
||
// const oArticleLoader = {
|
||
// iCurrentPage: 1,
|
||
// bLoading: false,
|
||
// sSlug: $('meta[name="article-slug"]').attr('content') || "freenode",
|
||
// oObserver: null,
|
||
//
|
||
// init() {
|
||
// const oSentinelEl = document.querySelector('#sentinel');
|
||
// if (!oSentinelEl) return;
|
||
//
|
||
// // 1. 哨兵物理补强:必须有高度和清浮动,确保它老老实实待在列表最下面
|
||
// $(oSentinelEl).css({
|
||
// 'height': '1px',
|
||
// 'clear': 'both',
|
||
// 'display': 'block',
|
||
// 'margin-top': '10px'
|
||
// });
|
||
//
|
||
// this.fSetupObserver(oSentinelEl);
|
||
//
|
||
// // 2. 初始自检:如果大屏幕一上来就看到哨兵,直接开火
|
||
// this.fCheckAndRefill();
|
||
// },
|
||
//
|
||
// // 设置/重置监听器
|
||
// fSetupObserver(oTarget) {
|
||
// if (this.oObserver) this.oObserver.disconnect();
|
||
//
|
||
// this.oObserver = new IntersectionObserver((aEntries) => {
|
||
// const oEntry = aEntries[0];
|
||
// // 只有真进入视口才加载
|
||
// if (oEntry.isIntersecting && !this.bLoading) {
|
||
// this.load();
|
||
// }
|
||
// }, {
|
||
// // 3. 精准控制:rootMargin 调小,防止加载过于提前
|
||
// rootMargin: '100px',
|
||
// threshold: 0.01
|
||
// });
|
||
//
|
||
// this.oObserver.observe(oTarget);
|
||
// },
|
||
//
|
||
// load() {
|
||
// if (this.bLoading) return;
|
||
// this.bLoading = true;
|
||
//
|
||
// $.get(`/${this.sSlug}`, {
|
||
// page: this.iCurrentPage + 1
|
||
// })
|
||
// .done((xResponse) => {
|
||
// const { sHtml, bHasMore, iPage } = xResponse;
|
||
//
|
||
// if (sHtml && sHtml.trim().length > 10) {
|
||
// // 渲染并渐显
|
||
// const oNewItems = $(sHtml).addClass('ajax-item-fade-in');
|
||
// $('.list-home').append(oNewItems);
|
||
//
|
||
// setTimeout(() => oNewItems.addClass('is-visible'), 50);
|
||
//
|
||
// // 4. 同步后端页码:单一事实来源
|
||
// this.iCurrentPage = iPage;
|
||
//
|
||
// if (!bHasMore) {
|
||
// this.fTerminateLoader();
|
||
// } else {
|
||
// // 5. 加载完后,递归检查是否填满了大屏幕
|
||
// this.fCheckAndRefill();
|
||
// }
|
||
// } else {
|
||
// this.fTerminateLoader();
|
||
// }
|
||
// })
|
||
// .fail(() => {
|
||
// console.error("[ArticleLoader] 链路异常");
|
||
// })
|
||
// .always(() => {
|
||
// // 6. 核心防抖:给浏览器 500ms 重新计算布局,防止哨兵位置还没更新就解锁
|
||
// setTimeout(() => {
|
||
// this.bLoading = false;
|
||
// }, 500);
|
||
// });
|
||
// },
|
||
//
|
||
// // 大屏适配逻辑:如果哨兵还在视口内,主动追载
|
||
// fCheckAndRefill() {
|
||
// // 给渲染留出足够的物理时间
|
||
// setTimeout(() => {
|
||
// const oSentinel = document.querySelector('#sentinel');
|
||
// if (!oSentinel || this.bLoading) return;
|
||
//
|
||
// const oRect = oSentinel.getBoundingClientRect();
|
||
// // 判定:哨兵顶部坐标 < 视口高度,说明它还在屏幕里
|
||
// if (oRect.top < window.innerHeight) {
|
||
// console.log("[ArticleLoader] 空间尚存,自动补货...");
|
||
// this.load();
|
||
// }
|
||
// }, 600); // 这里的延迟是解决“提前加载”的关键,给 DOM 变长的机会
|
||
// },
|
||
//
|
||
// fTerminateLoader() {
|
||
// if (this.oObserver) {
|
||
// this.oObserver.disconnect();
|
||
// this.oObserver = null;
|
||
// }
|
||
// $('#sentinel').fadeOut(300, function() { $(this).remove(); });
|
||
// console.log("[ArticleLoader] 任务圆满完成。");
|
||
// }
|
||
// };
|
||
//
|
||
// oArticleLoader.init();
|
||
//};
|
||
|
||
/**
|
||
* 匈牙利命名法:oObserver (对象), iCurrentPage (整数), bLoading (布尔)
|
||
*/
|
||
//export const fInitArticleLoader = () => {
|
||
// const oArticleLoader = {
|
||
// iCurrentPage: 1,
|
||
// bLoading: false,
|
||
// sSlug: $('meta[name="article-slug"]').attr('content') || "freenode",
|
||
// oObserver: null,
|
||
//
|
||
// init() {
|
||
// // 直接抓你页面上的哨兵
|
||
// const oSentinelEl = document.querySelector('#sentinel');
|
||
// if (!oSentinelEl) return;
|
||
//
|
||
// // 给哨兵一点颜色和高度(调试完可以把背景去掉,但高度要留着)
|
||
// $(oSentinelEl).css({
|
||
// 'height': '20px',
|
||
// 'width': '100%',
|
||
// 'display': 'block',
|
||
// 'clear': 'both',
|
||
// 'color': 'red',
|
||
// });
|
||
//
|
||
// this.fSetupObserver(oSentinelEl);
|
||
// },
|
||
//
|
||
// fSetupObserver(oTarget) {
|
||
// // 高标准:rootMargin 设为 0,让我们先精准捕获“露头”瞬间
|
||
// this.oObserver = new IntersectionObserver((aEntries) => {
|
||
// const oEntry = aEntries[0];
|
||
//
|
||
// // 只有当 isIntersecting 从 false 变为 true 时才会触发
|
||
// if (oEntry.isIntersecting && !this.bLoading) {
|
||
// this.load();
|
||
// }
|
||
// }, {
|
||
// threshold: [0, 0.1, 1.0] // 多阈值监听,增加灵敏度
|
||
// });
|
||
//
|
||
// this.oObserver.observe(oTarget);
|
||
// },
|
||
//
|
||
// load() {
|
||
// if (this.bLoading) return;
|
||
// this.bLoading = true;
|
||
//
|
||
// $.get(`/${this.sSlug}`, {
|
||
// page: this.iCurrentPage + 1
|
||
// })
|
||
// .done((xResponse) => {
|
||
// const { sHtml, bHasMore } = xResponse;
|
||
//
|
||
// if (sHtml && sHtml.trim().length > 5) {
|
||
// const oNewItems = $(sHtml).addClass('ajax-item-fade-in');
|
||
// $('.list-home').append(oNewItems);
|
||
//
|
||
// setTimeout(() => oNewItems.addClass('is-visible'), 50);
|
||
// this.iCurrentPage++;
|
||
//
|
||
// // 如果没数据了,当场撤编
|
||
// if (!bHasMore) {
|
||
// this.fTerminateLoader();
|
||
// }
|
||
// } else {
|
||
// this.fTerminateLoader();
|
||
// }
|
||
// })
|
||
// .fail(() => console.error("加载失败"))
|
||
// .always(() => {
|
||
// /**
|
||
// * 核心:这是解决“只加载一次”的关键!
|
||
// * 必须延迟解锁。如果内容渲染太快,浏览器还没来得及把哨兵推下视口,
|
||
// * 我们就解锁了,那么下一次滚动就不会触发进入事件。
|
||
// */
|
||
// setTimeout(() => {
|
||
// this.bLoading = false;
|
||
// console.log(`第 ${this.iCurrentPage} 页解锁,哨兵当前可见性:`, document.querySelector('#sentinel').getBoundingClientRect().top < window.innerHeight);
|
||
// }, 500); // 稍微长一点的延迟,确保 DOM 撑开
|
||
// });
|
||
// },
|
||
//
|
||
// fTerminateLoader() {
|
||
// if (this.oObserver) this.oObserver.disconnect();
|
||
// $('#sentinel').hide(); // 隐藏哨兵,防止它还占位
|
||
// }
|
||
// };
|
||
//
|
||
// oArticleLoader.init();
|
||
//};
|
||
|
||
/**
|
||
* 匈牙利命名法说明:
|
||
* f - function / o - object / i - integer / b - boolean / s - string
|
||
*/
|
||
|
||
export const fInitArticleLoader = () => {
|
||
const oArticleLoader = {
|
||
iCurrentPage: 1,
|
||
bLoading: false,
|
||
sSlug: "",
|
||
// 从 Meta 标签安全获取配置
|
||
iPageLimit: parseInt($('meta[name="page-limit"]').attr('content')) || 10,
|
||
|
||
init() {
|
||
const oBtn = $('.btn-more');
|
||
if (!oBtn.length) return;
|
||
|
||
this.sSlug = oBtn.data('slug') || "";
|
||
|
||
// 绑定点击事件
|
||
$(document).on('click', '.btn-more', (e) => {
|
||
e.preventDefault();
|
||
this.load();
|
||
});
|
||
|
||
console.log(`[ArticleLoader] 启动成功,Limit: ${this.iPageLimit}`);
|
||
},
|
||
|
||
load() {
|
||
|
||
if (this.bLoading) return;
|
||
|
||
const oBtn = $('.btn-more');
|
||
const oSpan = oBtn.find('span');
|
||
const sOriginalText = oSpan.text();
|
||
|
||
this.bLoading = true;
|
||
oBtn.addClass('is-loading');
|
||
oSpan.text('正在加载...');
|
||
|
||
$.get(`/${this.sSlug}`, {
|
||
page: this.iCurrentPage + 1
|
||
})
|
||
.done((xResponse) => {
|
||
// 解构 JSON 对象,拿到艺术的结晶
|
||
const { sHtml, bHasMore } = xResponse;
|
||
|
||
if (sHtml && sHtml.trim().length > 5) {
|
||
const oNewItems = $(sHtml).addClass('ajax-item-fade-in');
|
||
$('.list-home').append(oNewItems);
|
||
|
||
// 必须延迟,给浏览器留出渲染动画的时间
|
||
setTimeout(() => {
|
||
oNewItems.addClass('is-visible');
|
||
}, 50);
|
||
|
||
this.iCurrentPage++;
|
||
|
||
// 根据后端返回的 bHasMore 决定按钮生死
|
||
if (!bHasMore) {
|
||
this.fTerminateLoader(oBtn);
|
||
} else {
|
||
oSpan.text(sOriginalText);
|
||
}
|
||
} else {
|
||
this.fTerminateLoader(oBtn);
|
||
}
|
||
})
|
||
.fail((oXhr) => {
|
||
console.error("[ArticleLoader] 异常:", oXhr.status);
|
||
oSpan.text('加载失败,点击重试');
|
||
})
|
||
.always(() => {
|
||
this.bLoading = false;
|
||
oBtn.removeClass('is-loading');
|
||
});
|
||
},
|
||
|
||
// 抽取公用逻辑,这叫“代码洁癖”
|
||
fTerminateLoader(oBtn) {
|
||
oBtn.fadeOut(400, () => oBtn.remove());
|
||
console.log("[ArticleLoader] 全部加载完毕,任务结束。");
|
||
}
|
||
};
|
||
|
||
oArticleLoader.init();
|
||
};
|
||
|