import { ref, onMounted, onBeforeUnmount, nextTick, watch } from 'vue' /** * 图表背景位置调整 composable * @param {Object} options 配置选项 * @param {Ref} options.wrapperRef - 图表容器的 ref * @param {Ref} options.backgroundRef - 背景元素的 ref * @param {String} options.left - 背景 left 位置,如 '25%' 或 '50%',默认 '50%' * @param {String} options.top - 背景 top 位置,如 '50%',默认 '50%' * @param {String} options.offsetX - X 轴偏移值,如 '-51.5%' 或 '-50%',默认 '-50%' * @param {String} options.offsetY - Y 轴偏移值,如 '-39%' 或 '-50%',默认 '-50%' * @param {Ref} options.watchData - 可选,监听的数据变化,数据变化时重新调整位置 * @returns {Function} adjustBackgroundPosition - 手动调整背景位置的方法 */ export function useChartBackground(options = {}) { const { wrapperRef, backgroundRef, left = '50%', top = '50%', offsetX = '-50%', offsetY = '-50%', watchData = null } = options let resizeObserver = null let intersectionObserver = null let retryTimers = [] const clearRetryTimers = () => { if (!retryTimers.length) return retryTimers.forEach((t) => clearTimeout(t)) retryTimers = [] } // 调整背景位置 const adjustBackgroundPosition = () => { nextTick(() => { if (!wrapperRef?.value || !backgroundRef?.value) { return } // 初始化阶段经常出现:容器尚未可见/尺寸为 0(非全屏、tab、动画等) // 这种情况下先不对齐,等 ResizeObserver / IntersectionObserver 再触发 const rect = wrapperRef.value.getBoundingClientRect() if (!rect.width || !rect.height) return const background = backgroundRef.value // 使用百分比定位 + transform 微调(这是最可靠的方式) background.style.left = left background.style.top = top background.style.transform = `translate(${offsetX}, ${offsetY})` }) } // 初始化阶段多次“补偿对齐”,覆盖 Echarts 首次渲染/动画造成的延迟布局 const scheduleKickAlign = () => { clearRetryTimers() ;[0, 60, 180, 360, 800].forEach((ms) => { retryTimers.push( setTimeout(() => { adjustBackgroundPosition() }, ms) ) }) } // 窗口 resize 处理 const resizeHandler = () => { adjustBackgroundPosition() } // 如果提供了 watchData,监听数据变化(需要在 setup 阶段创建) if (watchData) { watch(watchData, () => { adjustBackgroundPosition() }, { deep: true }) } // 初始化 const init = () => { // 监听窗口 resize window.addEventListener('resize', resizeHandler) // 使用 ResizeObserver 监听容器尺寸变化 nextTick(() => { if (wrapperRef?.value && window.ResizeObserver) { resizeObserver = new ResizeObserver(() => { adjustBackgroundPosition() }) resizeObserver.observe(wrapperRef.value) } // 监听“从不可见到可见”,解决初始化时未对齐但热更新又正常的问题 if (wrapperRef?.value && window.IntersectionObserver) { intersectionObserver = new IntersectionObserver( (entries) => { const entry = entries?.[0] if (entry?.isIntersecting) { scheduleKickAlign() } }, { threshold: 0.01 } ) intersectionObserver.observe(wrapperRef.value) } // 初始化多次补偿对齐,确保图表渲染完成 scheduleKickAlign() }) } // 清理 const cleanup = () => { window.removeEventListener('resize', resizeHandler) clearRetryTimers() if (resizeObserver) { resizeObserver.disconnect() resizeObserver = null } if (intersectionObserver) { intersectionObserver.disconnect() intersectionObserver = null } } return { adjustBackgroundPosition, init, cleanup } }