/** * 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 };