clusterize.js 开源的长列表渲染库
1 前言
1.1 主要思想
- 不渲染所有的 DOM
- 它将列表拆分为 集群,然后显示当前滚动位置的元素
- 并在列表的顶部和底部添加额外的行来模拟表格的全高度(这样浏览器就会像显示完整列表一样显示滚动条 )
1.2 用法
<div id="scrollArea" ...>
<table>
<tbody id="contentArea" ...>
<tr>...</tr>
</tbody>
</table>
</div>
var data = ['<tr>…</tr>', '<tr>…</tr>', …];
var clusterize = new Clusterize({
rows: data,
scrollId: 'scrollArea',
contentId: 'contentArea'
});
1. options
Name | Require | 描述 |
---|---|---|
rows | 取决于 | 1. 存在现有的标记就不需要;2. 通过接收数据渲染则需要 |
scrollId | 必需 | 用作滚动区域的父标签的 Id 或 DOM 节点 |
contentId | 必需 | 将放置内容的标签的 ID 或 DOM 节点 |
rows_in_block | 可选的 | 块中的行数 默认50 |
blocks_in_cluster | 可选的 | 集群中的块数 默认4 |
tag | 可选的 | 支持的元素:间隔额外的行,空数据行 默认空 |
show_no_data_row | 可选的 | 无数据时是否显示“空”占位符行 默认true |
no_data_class | 可选的 | 无数据时的占位符内容 默认“无数据” |
no_data_text | 可选的 | 无数据时的占位符类名 默认“clusterize-no-data” |
keep_parity | 可选的 | 添加额外的标签以保持行的奇偶性 默认 true |
callbacks | 可选的 |
- callbacks 的属性
clusterWillChange
:新集群替换旧集群之前触发clusterChanged
:新集群替换旧集群后触发scrollingProgress
:滚动时触发,返回滚动进度位置
2. methods
Name | 参数 | 描述 |
---|---|---|
.update() | array | 数据更新时列表更新 |
.append() | array | 追加新数据到列表中 |
.prepend() | array | 添加新数据到列表中 |
.refresh() | boolean | 刷新行高 Clusterize必须始终知道当前行高 |
.getRowsAmount() | 获取总行数 | |
.getScrollProgress() | 获取当前滚动进度 | |
.clear() | 清除列表 | |
.destroy() | boolean | 销毁 clusterize实例 参数为true时从列表中删除所有数据,不指定or false则将所有隐藏数据插入列表 |
2 源码分析
2.1 准备工作
1. IIFE 立即执行函数 -- 避免污染全局环境
- 自执行匿名函数
(function() {})()
- 匿名函数
function() {}
拥有独立的词法作用域 - 再一次使用 () 创建了一个立即执行函数表达式,js 引擎到此将直接执行函数
;(function() {})()
闭包前写 ; 是为了防止代码压缩时,前面代码没写 ; 造成报错
2. 匿名函数 -- 定义一个适应所有环境的 js 模块
function(name, definition) {
if(typeof module != 'undefined') {
module.exports = definition()
} else if(typeof define == 'function' && typeof define.amd == 'object') {
define(definition)
} else {
this[name] = definition()
}
}
typeof module != 'undefined'
:使用 Commonjs 规范导出,能在 nodejs 中引入typeof define == 'function' && typeof define.amd == 'obj'
:使用 AMD 规范导出,能在浏览器引入- 其它:直接放在全局对象上
3. 传入的 definition 函数
function () {
// 检测ie版本
var ie = (function(){ /** ... */ } ())
// 检测mac版本
var is_mac = ...
// 核心代码
var Clusterize = function(data) {..}
Clusterize.prototype = {..}
// 解决兼容问题的函数
// 用于 绑定事件 的函数
function on(evt, element, fnc) { /** ... */ }
// 用于 解绑事件 的函数
function off(evt, element, fnc) { /** ... */ }
// 用于判断是否数组
function isArray(arr) { /** ... */ }
// 用于获取元素的样式
function getStyle(prop, elem) { /** ... */ }
}
2.2 核心代码
1. 自定义构造函数方式创建对象
var Clusterize = function(data) {}
Clusterize.prototype = {}
- 构造函数上主要定义 -- 被实例对象访问的属性和方法
- 原型对象上主要定义 -- 被构造函数访问的方法
2. Clusterize 构造函数
流程
- 定义全局属性
options
-- 使用传入的值 | 设置默认值 - 获取传入的
scrollId
contentId
绑定的元素 tabindex
令浏览器关注滚动列表- 定义私有参数
rows
cache
scroll_top
- 添加初始数据
- 恢复滚动位置
- 添加
scroll
事件resize
事件 - 定义全局方法
预备
this instanceof Clusterize
- 通过
this instanceof xxx
来判断有没有用 new 关键词调用
Clusterize 的原型对象在 this 的原型链上
- 为了防止不是
new Clusterize()
方式创建对象而是直接Clusterize()
调用函数出现错误
if(!(this instanceof Clusterize)) return new Clusterize(data);
var self = this;
- 很常见的避免
this
指向不明确不能直接拿到实例上的属性
属性
self.options
- 为实例创建时没设置的属性设置默认值 + 设置的属性值 写入
self.options
- 为实例创建时没设置的属性设置默认值 + 设置的属性值 写入
self.scollId_elem self.contentId_elem
- 获取传入的
scrollId contentId
绑定的元素
- 获取传入的
self.contentId_elem
元素设置属性tabindex
- 使用
tabindex
使得用户能够通过键盘tab
键关注(聚焦)滚动列表
- 使用
scrollTop
获取self.contentId_elem
元素顶部与可视区顶部的距离

-
scroll
事件- 修复了Mac上的滚动问题--使用防抖 只触发50ms内的最后一次滚动
- 集群号发生变化时 -- 检测数据变化并插入到DOM
- 有传入滚动时触发回调,则返回滚动进度位置
-
resize
事件- 设置开关 -- 只触发 100ms 内的最后一次
方法
-
destory()
- 事件解绑
- 置空列表数据 | 将所有隐藏数据插入列表
-
refresh()
- 单行高有变化时 || 强制刷新行高时 --> 更新列表
怎样判断行高是否变化呢?
-
update()
- 获取滚动进度暂存
- 行数 * 单行高 < 元素顶部与可视区顶部的距离 时
- 重置滚动距离为0 重置当前集群号为0
- 检测数据变化并插入到DOM
- 恢复滚动进度
-
其他方法
clear()
getRowsAmount()
getScrollProgress()
append()
prepend()
3. Clusterize.prototype 原型对象
1. getClusterNum()
获取当前集群块编号
/**
* 获取当前集群块编号
* @param {array} rows 构造函数传入的数据列表
* @return {string} 当前集群块编号
* */
- 当前集群号 = 滚动距离 / (集群总高度 - 单块的高度)
- 最大的集群数 = (行数 * 单行高) / (集群总高度 - 单块的高度)
- 返回 min(当前集群号, 最大的集群数)
每滚动4-1块,集群编号+1
2. insertToDOM()
检测数据变化并插入到DOM
初始化时 翻页 页码发生改变时 集群号改变时 触发
/**
* 检测数据变化并插入到DOM
* @param {array} rows 接收到的数据列表
* @param {object} cache 当前展示的数据
* @return {object}
* */
- 获取一个集群的高度
- 比较
当前展示的数据
与接收到的数据
的变化当前集群的数据
是否变化集群以上未渲染数据的高度
是否变化集群以下未渲染数据的高度
是否变化
当前集群的数据变化 || 集群以上未渲染数据的高度变化时
写入content_elem
的后代元素- 存在
集群以上未渲染数据的高度变化
时-- 创建标签- 需要添加额外的标签时,创建额外的行
layout.push(<tr class="clusterize-extra-row clusterize-top-space" style="集群以上未渲染数据的高度"></tr>)
- 创建标签
<tr>当前集群的数据</tr>
- 存在
集群以下未渲染数据
时,创建标签layout.push(<tr class="clusterize-extra-row clusterize-top-space" style="集群以下未渲染数据的高度"></tr>)
- 触发
传入的回调的函数
-- 替换前触发函数 - 设置
content_elem
元素的后代元素:this.html(layout.join(''))
- css 相关
- 触发
传入的回调的函数
-- 替换后触发函数
- 存在
集群以下未渲染数据的高度变化
时 写入content_elem
的后代元素content_elem
元素追加子节点<tr style="集群以下未渲染数据的高度"></tr>
- 怎样写入后代元素
- 怎样获取 集群以上未渲染数据,集群以下未渲染数据,需要展示的数据 ?
3. html()
设置 content_elem
元素的后代元素
/**
* 将数据列表写入tbody -- 适配 ie <= 9不允许对表元素使用 innerHTML
* @param {array} data 数据列表
* @return void
* */
content_elem.innerHTML = data
兼容 ie9以下版本
ie <= 9不允许对表元素使用 innerHTML
- 使用
ele.removeChild()
将content_elem
的子元素全部移除 - 将包含所有子元素的字符串截取出来 --> 转化为数组
- 使用
ele.appendChild()
将content_elem
的子元素写入
4. generate()
为当前滚动位置生成集群
/**
* 为当前滚动位置生成集群
* @param {array} rows 接收到的数据列表
* @return {object} { 集群以上未渲染数据,集群以下未渲染数据,开始的行数(不确定),需要展示的数据 }
* */
数据行数 < 一块的行数
时,返回{0, 0, 0, 当前数据 || 生成空行}
- 否则
开始的行数(序列号) = (总行数 - 单块行数) * 当前集群块编号 || 0
- 开始行数为0时,序列号为1
结束的行数 = 开始的行数 + 单块行数
隐藏的上半部分高度 = 开始的行数 * 单行高 || 0
隐藏的下半部分高度 = (总行数-结束的行数) * 单行高 || 0
- 返回
{, , , rows[开始的行数--结束的行数]}
/* Clusterize.js - v0.19.0 - 2021-12-19
http://NeXTs.github.com/Clusterize.js/
Copyright (c) 2015 Denis Lukov; Licensed GPLv3 */
;(function(name, definition) {
// 定义一个适应所有环境的js模块
if (typeof module != 'undefined') module.exports = definition();
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
else this[name] = definition();
}('Clusterize', function() {
"use strict"
// 检测ie9及以下版本 检测mac -- 为后续兼容判断做准备
var ie = (function(){
for( var v = 3,
el = document.createElement('b'),
all = el.all || [];
el.innerHTML = '<!--[if gt IE ' + (++v) + ']><i><![endif]-->',
all[0];
){}
return v > 4 ? v : document.documentMode;
}()),
is_mac = navigator.platform.toLowerCase().indexOf('mac') + 1;
var Clusterize = function(data) {
if( ! (this instanceof Clusterize))
return new Clusterize(data);
var self = this;
var defaults = {
rows_in_block: 50,
blocks_in_cluster: 4,
tag: null,
show_no_data_row: true,
no_data_class: 'clusterize-no-data',
no_data_text: 'No data',
keep_parity: true,
callbacks: {}
}
// 001 定义全局属性 -- 使用传入的值 | 设置默认值
self.options = {};
var options = ['rows_in_block', 'blocks_in_cluster', 'show_no_data_row', 'no_data_class', 'no_data_text', 'keep_parity', 'tag', 'callbacks'];
for(var i = 0, option; option = options[i]; i++) {
self.options[option] = typeof data[option] != 'undefined' && data[option] != null
? data[option]
: defaults[option];
}
// 002 获取传入的 scrollId contentId 绑定的元素
var elems = ['scroll', 'content'];
for(var i = 0, elem; elem = elems[i]; i++) {
self[elem + '_elem'] = data[elem + 'Id']
? document.getElementById(data[elem + 'Id'])
: data[elem + 'Elem'];
if( ! self[elem + '_elem'])
throw new Error("Error! Could not find " + elem + " element");
}
// 003 tabindex 能够使用 tab 键选中滚动列表
if( ! self.content_elem.hasAttribute('tabindex'))
self.content_elem.setAttribute('tabindex', 0);
// 私有参数
var rows = isArray(data.rows)
? data.rows
: self.fetchMarkup(),
cache = {},
scroll_top = self.scroll_elem.scrollTop;
// 004 添加初始数据
self.insertToDOM(rows, cache);
// 005 恢复滚动位置
self.scroll_elem.scrollTop = scroll_top;
// last_cluster 当前的集群号
var last_cluster = false,
scroll_debounce = 0,
pointer_events_set = false,
scrollEv = function() {
// 修复了Mac上的滚动问题--使用防抖 只触发50ms内的最后一次滚动
if (is_mac) {
if( ! pointer_events_set) self.content_elem.style.pointerEvents = 'none';
pointer_events_set = true;
clearTimeout(scroll_debounce);
scroll_debounce = setTimeout(function () {
self.content_elem.style.pointerEvents = 'auto';
pointer_events_set = false;
}, 50);
}
// 集群号发生变化时,检测数据变化并插入到DOM
if (last_cluster != (last_cluster = self.getClusterNum(rows)))
self.insertToDOM(rows, cache);
// 有传入滚动时触发函数,返回滚动进度位置
if (self.options.callbacks.scrollingProgress)
self.options.callbacks.scrollingProgress(self.getScrollProgress());
},
resize_debounce = 0,
resizeEv = function() {
clearTimeout(resize_debounce);
resize_debounce = setTimeout(self.refresh, 100);
}
// 006 添加scroll事件 resize事件
on('scroll', self.scroll_elem, scrollEv);
on('resize', window, resizeEv);
// 007 定义全局方法
self.destroy = function(clean) {
off('scroll', self.scroll_elem, scrollEv);
off('resize', window, resizeEv);
// clean为true则置空, 否则将所有隐藏数据插入列表
self.html((clean ? self.generateEmptyRow() : rows).join(''));
}
self.refresh = function(force) {
// 单行高有变化时(初始化时) || 强制刷新行高时 --> 更新列表
if(self.getRowsHeight(rows) || force) self.update(rows);
}
self.update = function(new_rows) {
rows = isArray(new_rows)
? new_rows
: [];
var scroll_top = self.scroll_elem.scrollTop; // 获取滚动距离
// 行数 * 单行高 < 滚动距离 时,即单屏可展示完所有数据
if(rows.length * self.options.item_height < scroll_top) {
// 重置滚动距离为0 重置当前集群号为
self.scroll_elem.scrollTop = 0;
last_cluster = 0;
}
// 检测数据变化并插入到DOM
self.insertToDOM(rows, cache);
// 恢复滚动进度
self.scroll_elem.scrollTop = scroll_top;
}
self.clear = function() {
self.update([]);
}
self.getRowsAmount = function() {
return rows.length;
}
self.getScrollProgress = function() {
return this.options.scroll_top / (rows.length * this.options.item_height) * 100 || 0;
}
var add = function(where, _new_rows) {
var new_rows = isArray(_new_rows)
? _new_rows
: [];
if( ! new_rows.length) return;
rows = where == 'append'
? rows.concat(new_rows)
: new_rows.concat(rows);
// 检测数据变化并插入到DOM
self.insertToDOM(rows, cache);
}
self.append = function(rows) {
add('append', rows);
}
self.prepend = function(rows) {
add('prepend', rows);
}
}
Clusterize.prototype = {
constructor: Clusterize, // 构造者对象指向 Clusterize
/**
* 不是通过rows参数传入数据时,获取子标签列表
* this.content_elem: <tbody>...</tbody>
* rows: [<tr>...</tr>, ..., <tr>...</tr>]
*/
fetchMarkup: function() {
var rows = [], rows_nodes = this.getChildNodes(this.content_elem);
while (rows_nodes.length) {
rows.push(rows_nodes.shift().outerHTML);
}
return rows;
},
/**
* 获取标签名 内容标签名称 标签高度 计算集群高度
* @param {array} rows 数据列表
* @param {array} cache 缓存数据--上一秒的渲染数据
* @return {object} void
* */
exploreEnvironment: function(rows, cache) {
var opts = this.options;
// 标签名 -- toby
opts.content_tag = this.content_elem.tagName.toLowerCase();
if( ! rows.length) return;
// 内容标签名 -- tr
if(ie && ie <= 9 && ! opts.tag) opts.tag = rows[0].match(/<([^>\s/]*)/)[1].toLowerCase();
// 行数 <= 1时,缓存中写入 3条重复的
if(this.content_elem.children.length <= 1) cache.data = this.html(rows[0] + rows[0] + rows[0]);
// 内容标签名 -- tr
if( ! opts.tag) opts.tag = this.content_elem.children[0].tagName.toLowerCase();
// 计算集群总高
this.getRowsHeight(rows);
},
/**
* 写入单行高、单块高、总行数、集群高度+ 判断单行高是否变化
* @param {array} rows 组件接收到的数据列表
* @return {boolean} 集群高度变化则返回true
* */
getRowsHeight: function(rows) {
var opts = this.options,
prev_item_height = opts.item_height;
opts.cluster_height = 0;
if( ! rows.length) return;
var nodes = this.content_elem.children;
if( ! nodes.length) return;
// 将子标签分为两部分 node是第二部分的第一个节点
var node = nodes[Math.floor(nodes.length / 2)];
opts.item_height = node.offsetHeight;
/**
* 考虑表的 border-spacing collapse 合并 separated 分隔 -> 获取相邻单元格边框之间的距离
* 一行数据的高度 item_height = 元素高度 + 相邻单元格边框之间的距离 = 30
*/
if(opts.tag == 'tr' && getStyle('borderCollapse', this.content_elem) != 'collapse')
opts.item_height += parseInt(getStyle('borderSpacing', this.content_elem), 10) || 0;
/**
* 考虑外边距
* 一行数据的高度 item_height = 元素高度 + max(top外边距, bottom外边距)
*/
if(opts.tag != 'tr') {
var marginTop = parseInt(getStyle('marginTop', node), 10) || 0;
var marginBottom = parseInt(getStyle('marginBottom', node), 10) || 0;
opts.item_height += Math.max(marginTop, marginBottom);
}
// 一个块的高度 block_height = 单行高度 * 块中的行数 = 30 * 50 = 1500
opts.block_height = opts.item_height * opts.rows_in_block;
// 一个集群的行数 rows_in_cluster = 集群中的块数 * 块中的行数 = 4 * 50 = 200
opts.rows_in_cluster = opts.blocks_in_cluster * opts.rows_in_block;
// 一个集群的高度 cluster_height = 集群中的块数 * 单块的高度 = 4 * 1500 = 6000
opts.cluster_height = opts.blocks_in_cluster * opts.block_height;
// 原来的单行高 != 现在的单行高 -> undefined != 30 true
return prev_item_height != opts.item_height;
},
/**
* 获取当前集群编号
* @param {array} rows 构造函数传入的数据列表
* @return {string} 当前集群块编号
* */
getClusterNum: function (rows) {
var opts = this.options;
opts.scroll_top = this.scroll_elem.scrollTop; // 滚动的像素数
// 集群总高度 - 单块的高度
var cluster_divider = opts.cluster_height - opts.block_height;
// 当前集群号 = 滚动距离 / (集群总高度 - 单块的高度)
var current_cluster = Math.floor(opts.scroll_top / cluster_divider);
// 最大的集群数 = (行数 * 单行高) / (集群总高度 - 单块的高度)
var max_cluster = Math.floor((rows.length * opts.item_height) / cluster_divider);
return Math.min(current_cluster, max_cluster);
},
// 如果没有提供数据,则生成空行
generateEmptyRow: function() {
var opts = this.options;
if( ! opts.tag || ! opts.show_no_data_row) return [];
var empty_row = document.createElement(opts.tag),
no_data_content = document.createTextNode(opts.no_data_text), td;
empty_row.className = opts.no_data_class;
if(opts.tag == 'tr') {
td = document.createElement('td');
// fixes #53
td.colSpan = 100;
td.appendChild(no_data_content);
}
empty_row.appendChild(td || no_data_content);
return [empty_row.outerHTML];
},
/**
* 为当前滚动位置生成集群
* @param {array} rows 要写入tbody的数据
* @return {object} {隐藏的上半部分高度,隐藏的下半部分高度,开始的行数(不确定),需要展示的行}
* */
generate: function (rows) {
var opts = this.options,
rows_len = rows.length;
// 数据行数 < 块中的行数时
if (rows_len < opts.rows_in_block) {
return {
top_offset: 0,
bottom_offset: 0,
rows_above: 0,
rows: rows_len ? rows : this.generateEmptyRow()
}
}
// 开始的行数 = (一个集群的行数 - 一个块的行数) * 当前集群编号 = (200-50)*0 = 0 || (200-50)*1=150
var items_start = Math.max((opts.rows_in_cluster - opts.rows_in_block) * this.getClusterNum(rows), 0),
// 结束的行数 = 开始的行数 + 一个集群的行数 = 200
items_end = items_start + opts.rows_in_cluster,
// 隐藏的上半部分高度 = 开始的行数 * 一行的高度 = 0 * 30 = 0
top_offset = Math.max(items_start * opts.item_height, 0),
// 隐藏的下半部分高度 = (总行数-结束的行数) * 一行的高度
bottom_offset = Math.max((rows_len - items_end) * opts.item_height, 0),
// 需要展示的行
this_cluster_rows = [],
rows_above = items_start;
if(top_offset < 1) {
rows_above++;
}
for (var i = items_start; i < items_end; i++) {
rows[i] && this_cluster_rows.push(rows[i]);
}
return {
top_offset: top_offset,
bottom_offset: bottom_offset,
rows_above: rows_above,
rows: this_cluster_rows
}
},
// 创建额外的行
renderExtraTag: function(class_name, height) {
var tag = document.createElement(this.options.tag),
clusterize_prefix = 'clusterize-';
tag.className = [clusterize_prefix + 'extra-row', clusterize_prefix + class_name].join(' ');
height && (tag.style.height = height + 'px');
return tag.outerHTML;
},
/**
* 检测数据变化并插入到DOM
* @param {array} rows 要写入tbody的数据
* @param {object} cache 当前展示的数据
* @return {object}
* */
insertToDOM: function(rows, cache) {
// !集群高度时,去获取
if( ! this.options.cluster_height) {
this.exploreEnvironment(rows, cache);
}
var data = this.generate(rows),
this_cluster_rows = data.rows.join(''),
// 当前集群的数据是否变化
this_cluster_content_changed = this.checkChanges('data', this_cluster_rows, cache),
// 集群以上未渲染数据的高度是否变化
top_offset_changed = this.checkChanges('top', data.top_offset, cache),
// 集群以下未渲染数据的高度是否变化
only_bottom_offset_changed = this.checkChanges('bottom', data.bottom_offset, cache),
callbacks = this.options.callbacks,
layout = [];
// 01 当前集群的数据变化 || 集群以上未渲染数据的高度变化
if(this_cluster_content_changed || top_offset_changed) {
// 011 存在 集群以上未渲染数据的高度变化时 -- 创建标签
if(data.top_offset) {
// 是否添加额外的标签 && 创建额外的行
this.options.keep_parity && layout.push(this.renderExtraTag('keep-parity'));
// <tr class="clusterize-extra-row clusterize-top-space" style="集群以上未渲染数据的高度"></tr>
layout.push(this.renderExtraTag('top-space', data.top_offset));
}
// 012 创建标签 <tr>当前集群的数据</tr>
layout.push(this_cluster_rows);
// 013 创建标签 <tr class="clusterize-extra-row clusterize-bottom-space" style="集群以下未渲染数据的高度"></tr>
data.bottom_offset && layout.push(this.renderExtraTag('bottom-space', data.bottom_offset));
// 014 触发 传入的回调的函数 -- 替换前触发
callbacks.clusterWillChange && callbacks.clusterWillChange();
// 015 替换
this.html(layout.join(''));
// 016 css相关
// 有序列表 则要添加序号渲染类名
this.options.content_tag == 'ol' && this.content_elem.setAttribute('start', data.rows_above);
// this.content_elem { counter-increment: clusterize-counter (data.rows_above-1)}
// counter-increment: clusterize-counter 0; width: 1599px;
// counter-increment: clusterize-counter 149; width: 1599px;
// Increment "clusterize-counter" by data.rows_above-1
this.content_elem.style['counter-increment'] = 'clusterize-counter ' + (data.rows_above-1);
// 017 触发 传入的回调的函数 -- 替换后触发
callbacks.clusterChanged && callbacks.clusterChanged();
} else if(only_bottom_offset_changed) {
// <tr ... style="集群以下未渲染数据的高度"></tr>
this.content_elem.lastChild.style.height = data.bottom_offset + 'px';
}
},
/**
* 将数据列表写入tbody -- 适配 ie <= 9不允许对表元素使用 innerHTML
* @param {array} data 数据列表
* @return void
* */
html: function(data) {
// 当前的 tbody
var content_elem = this.content_elem;
if(ie && ie <= 9 && this.options.tag == 'tr') {
var div = document.createElement('div'), last;
// div.innerHTML = '<table><tbody><tr>…</tr>...<tr>…</tr></tbody></table>';
div.innerHTML = '<table><tbody>' + data + '</tbody></table>';
// 将 content_elem 的子元素全部移除
while((last = content_elem.lastChild)) {
content_elem.removeChild(last);
}
// [<tr>…</tr>, ..., <tr>…</tr>]
var rows_nodes = this.getChildNodes(div.firstChild.firstChild);
// 往 content_elem 添加子元素
while (rows_nodes.length) {
content_elem.appendChild(rows_nodes.shift());
}
} else {
content_elem.innerHTML = data;
}
},
getChildNodes: function(tag) {
var child_nodes = tag.children, nodes = [];
for (var i = 0, ii = child_nodes.length; i < ii; i++) {
nodes.push(child_nodes[i]);
}
return nodes;
},
/**
* 当前集群的数据是否变化
* @param {array} value 要写入tbody的数据
* @param {array} cache[type] 当前写入tbody的数据
* @return {boolean}
* */
checkChanges: function(type, value, cache) {
var changed = value != cache[type];
cache[type] = value;
return changed;
}
}
// 解决兼容问题的函数
function on(evt, element, fnc) {
return element.addEventListener ? element.addEventListener(evt, fnc, false) : element.attachEvent("on" + evt, fnc);
}
function off(evt, element, fnc) {
return element.removeEventListener ? element.removeEventListener(evt, fnc, false) : element.detachEvent("on" + evt, fnc);
}
// 用于判断是否数组
function isArray(arr) {
return Object.prototype.toString.call(arr) === '[object Array]';
}
// 用于获取元素的样式
function getStyle(prop, elem) {
return window.getComputedStyle ? window.getComputedStyle(elem)[prop] : elem.currentStyle[prop];
}
return Clusterize;
}));
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)