602 lines
23 KiB
JavaScript
602 lines
23 KiB
JavaScript
export default class AGrid extends window.AObject {
|
|
constructor(options) {
|
|
super();
|
|
const defaultO = { element: ".AGrid", gridType: "row", hasRatio: false, desiredHeight: 200, gutter: 20 };
|
|
this._o = { ...defaultO, ...options };
|
|
|
|
|
|
this.ele = (typeof this._o.element === "string") ? document.querySelector(this._o.element) : this._o.element;
|
|
if (!this.ele) throw new Error("AGrid: element not found");
|
|
|
|
|
|
this.timeout = false;
|
|
this.ticking = false;
|
|
this.isRender = false;
|
|
this.loaded = false;
|
|
this.cancelFunc = null;
|
|
this.cancelResize = null;
|
|
|
|
this.addSystemEvent("resize", window, this.reLayout.bind(this));
|
|
window.app?.on?.("App_Scrolling", ((_) => {
|
|
if (window.app.checkVisible(this.ele) && !this.isRender) this.renderAnimation();
|
|
}).bind(this));
|
|
|
|
this.reElement();
|
|
}
|
|
|
|
observerInit(options) {
|
|
this._scrollEnd = {
|
|
...
|
|
{
|
|
enabled: true, // tắt/mở tính năng
|
|
root: null, // null = viewport; có thể truyền 1 element scroll container
|
|
rootMargin: "0px 0px 200px 0px", // nới 200px ở đáy để bắt sớm
|
|
threshold: 0.01, // chỉ cần chạm nhẹ là báo
|
|
onceUntilAppend: true // chỉ bắn 1 lần cho mỗi lần append; reset khi append thêm
|
|
}, ...options
|
|
};
|
|
this._io = null; // IntersectionObserver instance
|
|
this._ioTriggered = false;
|
|
}
|
|
_setupScrollEndIO() {
|
|
if (!this._scrollEnd.enabled) return;
|
|
if (this._io) return;
|
|
this._io = new IntersectionObserver(this._onScrollEndIntersect.bind(this), {
|
|
root: this._scrollEnd.root || null,
|
|
rootMargin: this._scrollEnd.rootMargin,
|
|
threshold: this._scrollEnd.threshold
|
|
});
|
|
}
|
|
_getLastGridItemEl() {
|
|
// Ưu tiên DOM order (an toàn cho append)
|
|
let el = this.ele.querySelector(".grid-item:last-of-type");
|
|
if (el) return el;
|
|
// Fallback theo Map (key lớn nhất)
|
|
if (this.listGrid && this.listGrid.size) {
|
|
let last = null, lastKey = -Infinity;
|
|
for (const [k, rec] of this.listGrid) { if (k > lastKey) { lastKey = k; last = rec; } }
|
|
if (last) return last.gridEl;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Bắt đầu quan sát item cuối
|
|
_observeLastItem() {
|
|
if (!this._scrollEnd?.enabled) return;
|
|
this._setupScrollEndIO();
|
|
if (!this._io) return;
|
|
|
|
const lastEl = this._getLastGridItemEl();
|
|
this._io.disconnect(); // luôn quan sát lại đúng item cuối
|
|
if (lastEl) this._io.observe(lastEl);
|
|
|
|
// Nếu dùng chế độ bắn 1 lần đến khi append thêm:
|
|
if (this._scrollEnd.onceUntilAppend) this._ioTriggered = false;
|
|
}
|
|
|
|
// Xử lý khi giao cắt
|
|
_onScrollEndIntersect(entries) {
|
|
|
|
for (const entry of entries) {
|
|
if (!entry.isIntersecting) continue;
|
|
if (this._ioTriggered && this._scrollEnd.onceUntilAppend) return;
|
|
|
|
this._ioTriggered = true;
|
|
//console.log(this);
|
|
this.trigger("scrollEnd", { instance: this });
|
|
|
|
// Nếu không dùng onceUntilAppend thì vẫn muốn chống spam khi còn trong khung:
|
|
if (!this._scrollEnd.onceUntilAppend) {
|
|
setTimeout(() => { this._ioTriggered = false; }, 800);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Public: cho phép tay reset cờ (nếu cần)
|
|
resetScrollEndTrigger() { this._ioTriggered = false; }
|
|
|
|
// Public: tắt/bật nhanh
|
|
enableScrollEnd(v = true) {
|
|
this._scrollEnd.enabled = !!v;
|
|
if (!v && this._io) { this._io.disconnect(); }
|
|
if (v) { this._setupScrollEndIO(); this._observeLastItem(); }
|
|
}
|
|
|
|
// ===== Khởi tạo lại list từ DOM hiện có =====
|
|
reElement() {
|
|
this.gridWidth = this.ele.clientWidth;
|
|
if (this._o.gridType === "waterfall") {
|
|
this.initCols();
|
|
}
|
|
this.listGrid = new Map();
|
|
this.keyI = 1;
|
|
|
|
const imageLoadPromises = [];
|
|
this.ele.querySelectorAll(".grid-item").forEach((el) => {
|
|
const img = el.querySelector("img.mImg");
|
|
const key = this.keyI;
|
|
|
|
this.listGrid.set(key, {
|
|
gridEl: el,
|
|
imgEl: img || null,
|
|
aRatio: el.hasAttribute("aRatio") ? parseFloat(el.getAttribute("aRatio")) || 0 : 0,
|
|
status: 0,
|
|
bounding: el.getBoundingClientRect()
|
|
});
|
|
|
|
const p = new Promise((resolve) => {
|
|
if (!img) {
|
|
const rec = this.listGrid.get(key);
|
|
rec.status = 1;
|
|
if (!rec.aRatio || !isFinite(rec.aRatio)) { rec.aRatio = 1; rec.gridEl.setAttribute("aRatio", rec.aRatio); }
|
|
resolve(); return;
|
|
}
|
|
|
|
const done = () => {
|
|
const rec = this.listGrid.get(key);
|
|
rec.status = 1;
|
|
if (img.naturalWidth > 0 && img.naturalHeight > 0) {
|
|
rec.aRatio = img.naturalWidth / img.naturalHeight;
|
|
} else if (!rec.aRatio || !isFinite(rec.aRatio)) {
|
|
rec.aRatio = 1;
|
|
}
|
|
rec.gridEl.setAttribute("aRatio", rec.aRatio);
|
|
rec.bounding = img.getBoundingClientRect();
|
|
this.removeSystemEvent?.("load", img, onLoad);
|
|
this.removeSystemEvent?.("error", img, onError);
|
|
resolve();
|
|
};
|
|
const onLoad = () => done();
|
|
const onError = () => { const r = this.listGrid.get(key); r.status = -1; done(); };
|
|
|
|
if (typeof img.decode === "function") {
|
|
img.decode().then(done).catch(() => {
|
|
if (img.complete) done();
|
|
else { this.addSystemEvent?.("load", img, onLoad); this.addSystemEvent?.("error", img, onError); }
|
|
});
|
|
} else if (img.complete) done();
|
|
else { this.addSystemEvent?.("load", img, onLoad); this.addSystemEvent?.("error", img, onError); }
|
|
});
|
|
|
|
imageLoadPromises.push(p);
|
|
this.keyI++;
|
|
});
|
|
|
|
Promise.allSettled(imageLoadPromises).then(() => {
|
|
this.renderAnimation();
|
|
window.requestTimeout?.(this.layoutInit.bind(this), 100);
|
|
});
|
|
}
|
|
appendElement(input) {
|
|
const items = this._normalizeToElements(input);
|
|
if (!items.length) return;
|
|
|
|
if (!this.listGrid) this.listGrid = new Map();
|
|
if (typeof this.keyI !== "number" || this.keyI < 1) this.keyI = this.listGrid.size + 1;
|
|
|
|
const newKeys = [];
|
|
|
|
for (const el of items) {
|
|
if (!el.classList.contains("grid-item")) el.classList.add("grid-item");
|
|
this.ele.appendChild(el);
|
|
|
|
const { key, rec } = this._registerItemSync(el); // đăng ký ngay (đồng bộ)
|
|
newKeys.push(key);
|
|
|
|
// ĐẶT VỊ TRÍ NGAY (không chờ ảnh) — giữ tối ưu single append
|
|
if (this._o.gridType === "waterfall") {
|
|
this._appendOptimizedSingleWaterfall(key, rec);
|
|
this._observeLastItem();
|
|
} else {
|
|
if (!this.listRows?.length) { this.isRender = false; this.layoutInit(); this.renderAnimation?.(); }
|
|
else this._appendOptimizedSingleRow(key, rec);
|
|
}
|
|
|
|
// BẮT ĐẦU load ảnh BẤT ĐỒNG BỘ (nếu có)
|
|
if (rec.imgEl) this._watchImageAsync(key, rec.imgEl);
|
|
}
|
|
|
|
if (newKeys.length > 1) {
|
|
this.isRender = false;
|
|
this.layoutInit();
|
|
if (window.app?.checkVisible?.(this.ele)) this.renderAnimation();
|
|
}
|
|
|
|
}
|
|
|
|
// Đăng ký item ngay lập tức + aRatio tạm nếu cần
|
|
_registerItemSync(el) {
|
|
el.style.width = this.colW + "px";
|
|
const img = el.querySelector("img.mImg");
|
|
const key = this.keyI++;
|
|
const rec = {
|
|
gridEl: el,
|
|
imgEl: img || null,
|
|
aRatio: el.hasAttribute("aRatio") ? parseFloat(el.getAttribute("aRatio")) : 0,
|
|
status: 0,
|
|
bounding: el.getBoundingClientRect()
|
|
};
|
|
|
|
if (img && img.complete && img.naturalWidth > 0 && img.naturalHeight > 0) {
|
|
// ảnh đã sẵn trong cache → dùng luôn tỉ lệ thật
|
|
rec.aRatio = img.naturalWidth / img.naturalHeight;
|
|
rec.status = 1;
|
|
el.setAttribute("aRatio", rec.aRatio);
|
|
rec.bounding = img.getBoundingClientRect();
|
|
} else {
|
|
// dùng tạm tỉ lệ 1 nếu thiếu
|
|
if (!rec.aRatio || !isFinite(rec.aRatio)) {
|
|
rec.aRatio = 1;
|
|
el.setAttribute("aRatio", rec.aRatio);
|
|
}
|
|
rec.status = img ? 0 : 1; // 0: đang chờ ảnh, 1: không có ảnh
|
|
}
|
|
|
|
this.listGrid.set(key, rec);
|
|
return { key, rec };
|
|
}
|
|
|
|
// Theo dõi load ảnh (BẤT ĐỒNG BỘ) → cập nhật tỉ lệ & re-layout (debounce)
|
|
_watchImageAsync(key, img) {
|
|
let done = false;
|
|
const finalize = (ok) => {
|
|
if (done) return; done = true;
|
|
const rec = this.listGrid.get(key);
|
|
if (!rec) return;
|
|
if (ok && img.naturalWidth > 0 && img.naturalHeight > 0) {
|
|
rec.aRatio = img.naturalWidth / img.naturalHeight;
|
|
rec.gridEl.setAttribute("aRatio", rec.aRatio);
|
|
rec.bounding = img.getBoundingClientRect();
|
|
rec.status = 1;
|
|
} else {
|
|
// lỗi ảnh → giữ aRatio hiện tại (đã có), mark lỗi
|
|
if (!rec.aRatio || !isFinite(rec.aRatio)) {
|
|
rec.aRatio = 1;
|
|
rec.gridEl.setAttribute("aRatio", rec.aRatio);
|
|
}
|
|
rec.status = -1;
|
|
}
|
|
|
|
this.layoutInit(); // reflow lại sau khi ảnh sẵn sàng
|
|
};
|
|
|
|
const onLoad = () => { cleanup(); finalize(true); };
|
|
const onError = () => { cleanup(); finalize(false); };
|
|
const cleanup = () => {
|
|
img.removeEventListener("load", onLoad);
|
|
img.removeEventListener("error", onError);
|
|
if (timer) clearTimeout(timer);
|
|
};
|
|
|
|
// Ưu tiên decode (async), fallback event load/error
|
|
if (typeof img.decode === "function") {
|
|
img.decode().then(() => finalize(true)).catch(() => {
|
|
if (img.complete) finalize(true);
|
|
else {
|
|
img.addEventListener("load", onLoad, { once: true });
|
|
img.addEventListener("error", onError, { once: true });
|
|
}
|
|
});
|
|
} else if (img.complete) {
|
|
finalize(true);
|
|
} else {
|
|
img.addEventListener("load", onLoad, { once: true });
|
|
img.addEventListener("error", onError, { once: true });
|
|
}
|
|
|
|
// Fallback timeout nếu load/error không bắn
|
|
/// const timer = setTimeout(() => finalize(false), 8000);
|
|
}
|
|
|
|
_normalizeToElements(input) {
|
|
if (typeof input === "string") {
|
|
const tpl = document.createElement("template");
|
|
tpl.innerHTML = input.trim();
|
|
return Array.from(tpl.content.children);
|
|
}
|
|
if (input instanceof HTMLElement) return [input];
|
|
if (input instanceof DocumentFragment) return Array.from(input.children);
|
|
if (input && (input instanceof NodeList || input instanceof HTMLCollection || Array.isArray(input))) {
|
|
return Array.from(input).filter(n => n instanceof HTMLElement);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
// ===== Layout chung =====
|
|
layoutInit() {
|
|
this.gridWidth = this.ele.clientWidth;
|
|
this.isRender = false;
|
|
if (this._o.gridType === "row") {
|
|
this.initRows();
|
|
this.layoutRows();
|
|
} else {
|
|
this.initCols();
|
|
this.restructCol();
|
|
this.layoutCols();
|
|
}
|
|
this._observeLastItem();
|
|
}
|
|
restructCol() {
|
|
this.ele.style.height = 'auto';
|
|
|
|
this.listGrid.forEach((v, k) => {
|
|
v.gridEl.style.top = '';
|
|
v.gridEl.style.left = '';
|
|
v.gridEl.style.width = this.colW;
|
|
v.gridEl.style.height = '';
|
|
v.gridEl.style.position = "relative";
|
|
v.bounding = v.gridEl.getBoundingClientRect();
|
|
});
|
|
}
|
|
reLayout() {
|
|
if (this.cancelResize) this.cancelResize();
|
|
if (!this.ticking) {
|
|
if (this.cancelFunc) this.cancelFunc();
|
|
const animationId = window.requestAnimationFrame(() => {
|
|
this.isRender = false;
|
|
this.sTime = new Date();
|
|
this.timeout = false;
|
|
this.ticking = true;
|
|
this.layoutInit();
|
|
this.ticking = false;
|
|
});
|
|
this.cancelFunc = (() => { cancelAnimationFrame(animationId); });
|
|
window.requestTimeout(() => { if (this.cancelFunc) this.cancelFunc(); this.layoutInit(); }, 500, (t) => { this.cancelResize = t; });
|
|
}
|
|
}
|
|
|
|
// ===== ROW (Justified) =====
|
|
initRows() {
|
|
this.listRows = [];
|
|
this.listRows.push(this.structureRow(this._o.desiredHeight));
|
|
}
|
|
|
|
layoutRows() {
|
|
let nRow = 0;
|
|
this.listGrid.forEach((_, k) => { nRow = this.pushEleToRow(nRow, k); });
|
|
this.ele.style.height = this.calcPosTop(0, this.listRows.length) + "px";
|
|
if (window.app?.checkVisible?.(this.ele)) this.renderAnimation();
|
|
}
|
|
|
|
pushEleToRow(nRow, k) {
|
|
const grid = this.listGrid.get(k);
|
|
const cRow = this.listRows[nRow];
|
|
const t1 = this._o.desiredHeight * (grid.aRatio * 1);
|
|
const denom = this.gridWidth - ((cRow.listElement.length - 1) * (1 * this._o.gutter));
|
|
const errorH = (cRow.rowW + t1) / (denom <= 0 ? 1 : denom);
|
|
|
|
if (errorH > 1.2 && cRow.listElement.length !== 0) {
|
|
cRow.rowH = this.reCalcWidth(cRow);
|
|
this.listRows.push(this.structureRow(this._o.desiredHeight));
|
|
return this.pushEleToRow(nRow + 1, k);
|
|
}
|
|
|
|
const g = (cRow.listElement.length > 0) ? this._o.gutter * 1 : 0;
|
|
cRow.listElement.push({ key: k, top: this.calcPosTop(0, nRow), left: (cRow.rowW + g), render: false });
|
|
cRow.rowW += t1;
|
|
cRow.numM = k;
|
|
|
|
if (errorH <= 1.2 && errorH >= 0.8) cRow.rowH = this.reCalcWidth(cRow);
|
|
return nRow;
|
|
}
|
|
|
|
calcPosTop(begin, end) {
|
|
if (begin === 0 && end === 0) return 0;
|
|
let rowH = 0;
|
|
for (let i = begin; i <= end - 1; i++) rowH += this.listRows[i].rowH + 1 * this._o.gutter;
|
|
return rowH;
|
|
}
|
|
|
|
reCalcWidth(cRow) {
|
|
const rowW = this.gridWidth - (cRow.listElement.length - 1) * this._o.gutter * 1;
|
|
let tRate = 0;
|
|
for (let i = 0; i < cRow.listElement.length; i++) tRate += this.listGrid.get(cRow.listElement[i].key).aRatio * 1;
|
|
const rrowH = rowW / (tRate === 0 ? 1 : tRate);
|
|
|
|
cRow.rowW = 0;
|
|
for (let i = 0; i < cRow.listElement.length; i++) {
|
|
if (i === 0) cRow.listElement[i].left = 0;
|
|
else {
|
|
const prev = cRow.listElement[i - 1];
|
|
const wPrev = (this.listGrid.get(prev.key).aRatio * 1) * rrowH;
|
|
cRow.rowW += wPrev;
|
|
cRow.listElement[i].left = prev.left + wPrev + this._o.gutter * 1;
|
|
}
|
|
}
|
|
return rrowH;
|
|
}
|
|
|
|
_appendOptimizedSingleRow(key, rec) {
|
|
let lastIdx = this.listRows.length - 1;
|
|
if (lastIdx < 0) { this.initRows(); lastIdx = 0; }
|
|
const cRow = this.listRows[lastIdx];
|
|
|
|
const t1 = this._o.desiredHeight * (rec.aRatio || 1);
|
|
const denom = this.gridWidth - (cRow.listElement.length - 1) * this._o.gutter;
|
|
const errorH = (cRow.rowW + t1) / (denom <= 0 ? 1 : denom);
|
|
const topBase = this.calcPosTop(0, lastIdx);
|
|
|
|
if (errorH > 1.2 && cRow.listElement.length !== 0) {
|
|
cRow.rowH = this.reCalcWidth(cRow);
|
|
// render lại row cũ
|
|
for (let i = 0; i < cRow.listElement.length; i++) {
|
|
const it = cRow.listElement[i];
|
|
const g = this.listGrid.get(it.key);
|
|
const w = cRow.rowH * (g.aRatio || 1);
|
|
this._applyGridStyle(g.gridEl, it.top, it.left, w, cRow.rowH);
|
|
if (g.imgEl) { g.imgEl.style.width = `${w.toFixed(2)}px`; g.imgEl.style.height = `${(+cRow.rowH).toFixed(2)}px`; }
|
|
}
|
|
|
|
const newRow = this.structureRow(this._o.desiredHeight);
|
|
this.listRows.push(newRow);
|
|
const topNew = this.calcPosTop(0, this.listRows.length - 1);
|
|
const leftNew = 0;
|
|
newRow.listElement.push({ key, top: topNew, left: leftNew, render: false });
|
|
newRow.rowW += t1;
|
|
|
|
const wNew = this._o.desiredHeight * (rec.aRatio || 1);
|
|
this._applyGridStyle(rec.gridEl, topNew, leftNew, wNew, this._o.desiredHeight);
|
|
} else {
|
|
const gspace = cRow.listElement.length > 0 ? this._o.gutter : 0;
|
|
const left = cRow.rowW + gspace;
|
|
cRow.listElement.push({ key, top: topBase, left, render: false });
|
|
cRow.rowW += t1;
|
|
|
|
if (errorH <= 1.2 && errorH >= 0.8) cRow.rowH = this.reCalcWidth(cRow);
|
|
|
|
for (let i = 0; i < cRow.listElement.length; i++) {
|
|
const it = cRow.listElement[i];
|
|
const g = this.listGrid.get(it.key);
|
|
const w = cRow.rowH * (g.aRatio || 1);
|
|
this._applyGridStyle(g.gridEl, it.top, it.left, w, cRow.rowH);
|
|
if (g.imgEl) { g.imgEl.style.width = `${w.toFixed(2)}px`; g.imgEl.style.height = `${(+cRow.rowH).toFixed(2)}px`; }
|
|
}
|
|
}
|
|
|
|
this.ele.style.height = this.calcPosTop(0, this.listRows.length) + "px";
|
|
this.isRender = true;
|
|
}
|
|
|
|
// ===== WATERFALL (Masonry) =====
|
|
initCols() {
|
|
this.wWidth = window.innerWidth || document.body.clientWidth;
|
|
const colNum = this._resolveColNum(this.wWidth);
|
|
this._buildCols(colNum, this.gridWidth);
|
|
}
|
|
|
|
layoutCols() {
|
|
// phân bổ tuần tự sang cột (theo code gốc)
|
|
let i = 0, col = 0, l = 0;
|
|
this.listCols.forEach(c => { c.listElement = []; c.colH = 0; });
|
|
|
|
this.listGrid.forEach((v, k) => {
|
|
let t = 0, cVal;
|
|
const pC = v.gridEl.querySelector(".grid-content");
|
|
let hC = pC ? pC.clientHeight : 0;
|
|
if (i % this.listCols.length === 0) { col = 0; cVal = this.listCols[col]; l = 0; }
|
|
else { col++; cVal = this.listCols[col]; l += this.colW + 1 * this._o.gutter; }
|
|
|
|
t = cVal.colH;
|
|
hC = hC + this.colW / (1 * v.aRatio);
|
|
// this._applyGridStyle(v.gridEl, t, l, this.colW, hC);
|
|
cVal.listElement.push({ key: k, top: t, left: l, height: hC, render: false });
|
|
cVal.colH = t + hC + 1 * this._o.gutter;
|
|
i++;
|
|
});
|
|
|
|
this.ele.style.height = this.getColMaxHeight() + "px";
|
|
if (window.app?.checkVisible?.(this.ele)) this.renderAnimation();
|
|
}
|
|
_resolveColNum(width) {
|
|
const bp = this._o.breakpoints;
|
|
|
|
|
|
if (bp && typeof bp === "object") {
|
|
const keys = Object.keys(bp)
|
|
.map(k => +k)
|
|
.filter(Number.isFinite)
|
|
.sort((a, b) => b - a);
|
|
|
|
for (const k of keys) {
|
|
if (width >= k) {
|
|
const c = +bp[k]?.col;
|
|
if (c > 0) return c;
|
|
}
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
_buildCols(colNum, width) {
|
|
const gutter = Number(this._o.gutter) || 0;
|
|
this.colW = (width - (colNum - 1) * gutter) / colNum;
|
|
this.listCols = [];
|
|
let l = 0;
|
|
for (let i = 0; i < colNum; i++) {
|
|
this.listCols.push({ listElement: [], colH: 0, left: l, colW: this.colW });
|
|
l += this.colW + 1 * this._o.gutter;
|
|
}
|
|
}
|
|
_appendOptimizedSingleWaterfall(key, rec) {
|
|
// Chọn cột thấp nhấtlistCols
|
|
let minIdx = 0;
|
|
for (let i = 1; i < this.listCols.length; i++) {
|
|
if (this.listCols[i].colH < this.listCols[minIdx].colH) minIdx = i;
|
|
}
|
|
const col = this.listCols[minIdx];
|
|
const pC = rec.gridEl.querySelector(".grid-content");
|
|
const hC = pC ? pC.getBoundingClientRect().height : 0;
|
|
|
|
const height = (rec.aRatio == 0) ? rec.gridEl.clientHeight : hC + (this.colW / (1 * rec.aRatio));
|
|
|
|
const top = col.colH;
|
|
const left = col.left;
|
|
|
|
col.listElement.push({ key, top, left, height, render: true });
|
|
col.colH = top + height + 1 * (this._o.gutter || 0);
|
|
this._applyGridStyle(rec.gridEl, top, left, this.colW, height);
|
|
this.ele.style.height = this.getColMaxHeight() + "px";
|
|
this.isRender = true;
|
|
}
|
|
getColMaxHeight() {
|
|
let m = 0;
|
|
for (let i = 0; i < this.listCols.length; i++) if (m <= this.listCols[i].colH) m = this.listCols[i].colH;
|
|
return m;
|
|
}
|
|
|
|
// ===== Render chung =====
|
|
renderAnimation() {
|
|
this.isRender = true;
|
|
if (this._o.gridType === "row") {
|
|
if (this.listRows == null) return;
|
|
this.listRows.forEach((row) => {
|
|
for (let i = 0; i < row.listElement.length; i++) {
|
|
const it = row.listElement[i];
|
|
const grid = this.listGrid.get(it.key);
|
|
const w = row.rowH * (grid.aRatio || 1);
|
|
this._applyGridStyle(grid.gridEl, it.top, it.left, w, row.rowH);
|
|
if (grid.imgEl) {
|
|
grid.imgEl.style.width = `${w.toFixed(2)}px`;
|
|
grid.imgEl.style.height = `${(+row.rowH).toFixed(2)}px`;
|
|
}
|
|
}
|
|
});
|
|
} else {
|
|
if (this.listCols == null) return;
|
|
this.listCols.forEach((col) => {
|
|
for (let i = 0; i < col.listElement.length; i++) {
|
|
const it = col.listElement[i];
|
|
const grid = this.listGrid.get(it.key);
|
|
this._applyGridStyle(grid.gridEl, it.top, it.left, this.colW, it.height);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
_applyGridStyle(el, top, left, width, height) {
|
|
const s = el.style;
|
|
if (s.position !== "absolute") s.position = "absolute";
|
|
s.top = `${(+top).toFixed(2)}px`;
|
|
s.left = `${(+left).toFixed(2)}px`;
|
|
s.width = `${(+width).toFixed(2)}px`;
|
|
s.height = `${(+height).toFixed(2)}px`;
|
|
}
|
|
structureCol(colN) { return { listElement: [], colNum: colN, colH: 0, colW: 0 }; }
|
|
structureRow(rowH = 0) { return { listElement: [], rowH, status: 0, numM: 0, rowW: 0 }; }
|
|
|
|
// ===== Utility =====
|
|
empty() {
|
|
this.ele.innerHTML = '';
|
|
this.listGrid?.clear();
|
|
this.listRows = [];
|
|
this.listCols = [];
|
|
this.keyI = 1;
|
|
this.isRender = false;
|
|
this.gridWidth = 0;
|
|
this.ele.style.height = '0px';
|
|
this.gridWidth = this.ele.clientWidth;
|
|
this.initCols();
|
|
|
|
|
|
}
|
|
}
|