yyb
35 分钟以前 04e562f9fe7aee43694fecc88753c68523dde216
src/hooks/useChartBackground.js
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,133 @@
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
  }
}