113 lines
3.3 KiB
JavaScript
113 lines
3.3 KiB
JavaScript
/**
|
||
* WxMeasureAdapter — 微信小程序 wx.createSelectorQuery 实现 IMeasureAdapter。
|
||
*
|
||
* 由于小程序不支持 ResizeObserver,使用 BoundingClientRect 查询容器尺寸,
|
||
* 并提供 `poll(intervalMs)` 方法做周期性尺寸检测(模拟 resize 通知)。
|
||
*
|
||
* 使用方式:
|
||
* const m = createWxMeasureAdapter(component, '#tc-viewport', 14, 16.8);
|
||
* m.onResize(() => {
|
||
* const { widthPx, heightPx } = m.measureContainer();
|
||
* const { widthPx: cw, heightPx: ch } = m.measureChar();
|
||
* const cols = Math.max(20, Math.floor(widthPx / cw));
|
||
* const rows = Math.max(8, Math.floor(heightPx / ch));
|
||
* // ... resize terminal
|
||
* });
|
||
* m.startPoll(500);
|
||
* // 在 component detached / onUnload 中:
|
||
* m.stopPoll();
|
||
*/
|
||
|
||
/**
|
||
* @param {object} ctx - 小程序 Component 实例(this),用于 createSelectorQuery 作用域
|
||
* @param {string} selector - CSS 选择器,如 '#tc-viewport'
|
||
* @param {number} charWidthPx - 等宽字符宽度像素(由组件在初始化时测量或硬编码)
|
||
* @param {number} charHeightPx - 行高像素
|
||
*/
|
||
function createWxMeasureAdapter(ctx, selector, charWidthPx, charHeightPx) {
|
||
let _containerW = 0;
|
||
let _containerH = 0;
|
||
let _pollTimer = null;
|
||
const _callbacks = [];
|
||
|
||
/** 实现 IMeasureAdapter.measureChar */
|
||
function measureChar() {
|
||
return {
|
||
widthPx: charWidthPx || 8,
|
||
heightPx: charHeightPx || 16,
|
||
};
|
||
}
|
||
|
||
/** 实现 IMeasureAdapter.measureContainer */
|
||
function measureContainer() {
|
||
return {
|
||
widthPx: _containerW,
|
||
heightPx: _containerH,
|
||
};
|
||
}
|
||
|
||
/** 实现 IMeasureAdapter.onResize */
|
||
function onResize(cb) {
|
||
_callbacks.push(cb);
|
||
return function off() {
|
||
const idx = _callbacks.indexOf(cb);
|
||
if (idx >= 0) _callbacks.splice(idx, 1);
|
||
};
|
||
}
|
||
|
||
/** 主动刷新容器尺寸(异步) */
|
||
function refresh(onDone) {
|
||
const query = ctx.createSelectorQuery();
|
||
query.select(selector).boundingClientRect(function(rect) {
|
||
if (!rect) return;
|
||
const newW = rect.width || 0;
|
||
const newH = rect.height || 0;
|
||
if (newW !== _containerW || newH !== _containerH) {
|
||
_containerW = newW;
|
||
_containerH = newH;
|
||
for (let i = 0; i < _callbacks.length; i++) {
|
||
try { _callbacks[i](); } catch(e) { /* isolation */ }
|
||
}
|
||
}
|
||
if (typeof onDone === 'function') onDone({ widthPx: _containerW, heightPx: _containerH });
|
||
}).exec();
|
||
}
|
||
|
||
/** 启动周期性轮询(模拟 ResizeObserver)。intervalMs 建议 300~1000ms */
|
||
function startPoll(intervalMs) {
|
||
stopPoll();
|
||
const ms = intervalMs || 500;
|
||
// 立刻执行一次
|
||
refresh();
|
||
_pollTimer = setInterval(function() { refresh(); }, ms);
|
||
}
|
||
|
||
/** 停止轮询 */
|
||
function stopPoll() {
|
||
if (_pollTimer !== null) {
|
||
clearInterval(_pollTimer);
|
||
_pollTimer = null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 一次性测量(供初始化使用)。
|
||
* @param {function} cb - (result: { widthPx, heightPx }) => void
|
||
*/
|
||
function measureOnce(cb) {
|
||
refresh(cb);
|
||
}
|
||
|
||
return {
|
||
measureChar: measureChar,
|
||
measureContainer: measureContainer,
|
||
onResize: onResize,
|
||
refresh: refresh,
|
||
startPoll: startPoll,
|
||
stopPoll: stopPoll,
|
||
measureOnce: measureOnce,
|
||
};
|
||
}
|
||
|
||
module.exports = { createWxMeasureAdapter: createWxMeasureAdapter };
|