Files
layui/src/modules/flow.js
贤心 060141c8df refactor(flow): 使用 component 模块重构组件 (#2860)
* refactor(flow): 使用 component 模块重构组件

* test(flow): 优化测试用例

* feat(component): 新增 index 实例成员

* fix(flow): 修复特殊场景下的事件冲突

* fix(flow): update
2025-10-20 12:16:11 +08:00

228 lines
7.0 KiB
JavaScript

/**
* flow
* 流加载组件
*/
layui.define(['i18n', 'component'], function(exports) {
"use strict";
var $ = layui.$;
var i18n = layui.i18n;
// 创建组件
var component = layui.component({
name: 'flow',
CONST: {
ELEM_LOAD: '<i class="layui-anim layui-anim-rotate layui-anim-loop layui-icon layui-icon-loading-1"></i>',
ELEM_MORE: 'layui-flow-more',
FLOW_SCROLL_EVENTS: 'scroll.lay_flow_scroll',
LAZYIMG_SCROLL_EVENTS: 'scroll.lay_flow_lazyimg_scroll',
},
// 渲染
render: function() {
var that = this;
var options = that.config;
var page = 0;
var locked;
var finished;
var elem = options.elem;
if (!elem[0]) return;
var scrollElem = $(options.scrollElem || document); // 滚动条所在元素
var threshold = 'mb' in options ? options.mb : 50; // 临界距离
var isAuto = 'isAuto' in options ? options.isAuto : true; // 否自动滚动加载
var moreText = options.moreText || i18n.$t('flow.loadMore'); // 手动加载时,加载更多按钮文案
var endText = options.end || i18n.$t('flow.noMore'); // “末页”显示文案
var direction = options.direction || 'bottom';
var isTop = direction === 'top';
// 滚动条所在元素是否为 document
var notDocument = options.scrollElem && options.scrollElem !== document;
// 加载更多
var ELEM_TEXT = '<cite>' + moreText + '</cite>';
var $more = $('<div class="'+ CONST.ELEM_MORE +'"><a href="javascript:;">'+ ELEM_TEXT +'</a></div>');
elem.find('.'+ CONST.ELEM_MORE).remove(); // 清除旧的「加载更多」元素
elem[isTop ? 'prepend' : 'append']($more);
// 加载下一个元素
var next = function(content, status) {
var scrollHeightStart = notDocument
? scrollElem.prop('scrollHeight')
: document.documentElement.scrollHeight;
var scrollTopStart = scrollElem.scrollTop();
$more[isTop ? 'after' : 'before'](content);
status = status == 0 ? true : null;
status ? $more.html(endText) : $moreBtn.html(ELEM_TEXT);
finished = status;
locked = null;
// 如果允许图片懒加载
if (options.isLazyimg) {
component.lazyimg({
elem: options.elem.find('img[lay-src]'),
scrollElem: options.scrollElem,
direction: options.direction,
id: options.id
});
}
if (isTop) {
var scrollHeightEnd = notDocument ? scrollElem.prop('scrollHeight') : document.documentElement.scrollHeight;
if (page === 1) {
// 首次渲染后滑动到底部
scrollElem.scrollTop(scrollHeightEnd);
} else if(page > 1) {
var nextElementHeight = scrollHeightEnd - scrollHeightStart;
scrollElem.scrollTop(scrollTopStart + nextElementHeight);
}
}
};
var $moreBtn = $more.find('a');
// 触发请求
var done = (function fn() {
locked = true;
$moreBtn.html(CONST.ELEM_LOAD);
typeof options.done === 'function' && options.done(++page, next);
return fn;
})();
// 不自动滚动加载
$moreBtn.on('click', function() {
if (finished) return;
locked || done();
});
if (!isAuto) return that;
// 滚动条滚动事件
var timer;
var FLOW_SCROLL_EVENTS = CONST.FLOW_SCROLL_EVENTS + '_' + options.id;
scrollElem.off(FLOW_SCROLL_EVENTS).on(FLOW_SCROLL_EVENTS, function() {
var othis = $(this), top = othis.scrollTop();
if (timer) clearTimeout(timer);
// 如果已经结束,或者元素处于隐藏状态,则不执行滚动加载
if (finished || !elem.width()) return;
timer = setTimeout(function() {
// 计算滚动所在容器的可视高度
var height = notDocument ? othis.height() : $(window).height();
// 计算滚动所在容器的实际高度
var scrollHeight = notDocument
? othis.prop('scrollHeight')
: document.documentElement.scrollHeight;
// 临界点
if(!isTop ? scrollHeight - top - height <= threshold : top <= threshold){
locked || done();
}
}, 100);
});
}
});
var CONST = component.CONST;
/**
* 扩展组件原型方法
*/
// 保留原接口,确保向下兼容
$.extend(component, {
load: function(options) {
return component.render(options);
},
// 图片懒加载
lazyimg: function(options) {
options = options || {};
var scrollElem = $(options.scrollElem || document); // 滚动条所在元素
var elem = options.elem || 'img';
var direction = options.direction || 'bottom';
var isTop = direction === 'top';
var index = 0;
// 滚动条所在元素是否为 document
var notDocument = options.scrollElem && options.scrollElem !== document;
// 显示图片
var render = (function fn(othis) {
var $elem = $(elem);
// 计算滚动所在容器的可视高度
var height = notDocument ? scrollElem.height() : $(window).height();
var start = scrollElem.scrollTop();
var end = start + height;
var show = function(item) {
var elemTop = notDocument ? function(){
return item.offset().top - scrollElem.offset().top + start;
}() : item.offset().top;
/* 始终只加载在当前屏范围内的图片 */
if ((isTop ? elemTop + item.height() : elemTop) >= start && elemTop <= end) {
if(item.attr('lay-src')){
var src = item.attr('lay-src');
layui.img(src, function() {
var next = $elem.eq(index);
item.attr('src', src).removeAttr('lay-src');
/* 当前图片加载就绪后,检测下一个图片是否在当前屏 */
next[0] && render(next);
index++;
}, function() {
item.removeAttr('lay-src');
});
}
}
};
if (othis) {
show(othis);
} else {
// 计算未加载过的图片
for (var i = 0; i < $elem.length; i++) {
var item = $elem.eq(i), elemTop = notDocument ? function(){
return item.offset().top - scrollElem.offset().top + start;
}() : item.offset().top;
show(item);
index = i;
// 如果图片的 top 坐标,超出了当前屏,则终止后续图片的遍历
if (elemTop > end) break;
}
}
return fn;
})();
// 滚动事件
var timer;
var id = options.id || '';
var LAZYIMG_SCROLL_EVENTS = CONST.LAZYIMG_SCROLL_EVENTS + '_' + id;
scrollElem.off(LAZYIMG_SCROLL_EVENTS).on(LAZYIMG_SCROLL_EVENTS, function() {
if (timer) clearTimeout(timer)
timer = setTimeout(function(){
render();
}, 50);
});
return render;
}
});
exports(CONST.MOD_NAME, component);
});