Skip to content
vue
<template>
  <div
      ref="autoScaleBox"
      class="auto-scale-box"
      :style="style">
    <slot></slot>
  </div>
</template>

<script setup>

import {computed, nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch} from "vue";

import {debounce} from './debounce.js';

const autoScaleBox = ref(null);

const defaultProps = defineProps({
     /**
     * 设计稿宽度
     */
    width: {
        type: [Number, String],
        default: 1920,
    },
    /**
     * 设计稿高度
     */
    height: {
        type: [Number, String],
        default: 1080,
    },
    /**
     * 缩放类型 1: 强制铺满 2: 按比例缩放 3: 适应宽度 4: 适应高度 默认值为1
     */
    scaleType: {
        type: Number,
        default: 1,
        validator(value) {
            // The value must match one of these strings
            return [1, 2, 3, 4].includes(value)
        }
    },
    /**
     * 自适应缩放防抖延迟时间(ms) 默认 100
     */
    delay: {
        type: Number,
        default: 100,
    },
    /**
     * 不被缩放的元素id
     */
    reverseScalingIds: {
        type: [Array, String],
        default: null
    },
    /**
     * 获取宽高的时候是否使用父元素的宽高 默认false
     */
    parent: {
        type: Boolean,
        default: false
    }
});

const props = toRefs(defaultProps)

const emit = defineEmits(['change'])


const transform = ref('')
const top = ref('')

const style = computed(() => {
  const w = isNaN(props.width.value) ? props.width.value : props.width.value + 'px'
  const h = isNaN(props.height.value) ? props.height.value : props.height.value + 'px'
  return {
    transform: transform.value,/*'scale(var(--scale)) translate(-50%, -50%)'*/
    width: w,
    height: h,
    top: top.value,
  }
});

/**
 * 缩放类型
 * @returns {string[]|number[]}
 */
function getScale() {
  const designWidth = props.width.value
  const designHeight = props.height.value

  let docWidth = window.innerWidth
  let docHeight = window.innerHeight
  if (props.parent) {
    if (!autoScaleBox.value) {
      console.warn('元素未渲染');
      return [1, 1]
    }
    // 获取父元素的宽高
    const {width, height} = autoScaleBox.value.parentElement.getBoundingClientRect()
    docWidth = width
    docHeight = height
  }

  // 强制铺满
  if (props.scaleType.value === 1) {
    return [`${docWidth / designWidth},${docHeight / designHeight}`, `${designWidth / docWidth},${designHeight / docHeight}`]
  }
  // 按比例缩放
  if (props.scaleType.value === 2) {

    const wh1 = docHeight / designHeight
    const wh2 = designHeight / docHeight;
    const ww1 = docWidth / designWidth;
    const ww2 = designWidth / docWidth;
    return ww1 < wh1 ? [ww1, ww2] : [wh1, wh2];
  }

  // 适应宽度
  if (props.scaleType.value === 3) {
    return [docWidth / designWidth, designWidth / docWidth]

  }

  // 适应高度
  if (props.scaleType.value === 4) {
    return [docHeight / designHeight, designHeight / docHeight]

  }


  return [1, 1]
}

function setScale() {

  let [scale, scale1] = getScale()
  transform.value = `scale(${scale})`

  //  如果是按比列适配则设置top
  if (props.scaleType.value === 2) {
    nextTick(() => {
      setTimeout(() => {
        top.value = (autoScaleBox.value.parentElement.getBoundingClientRect().height - autoScaleBox.value.getBoundingClientRect().height) / 2 + 'px'
      }, 400)
    })
  } else {
    top.value = '0'
  }
  // 将缩放比例传给父组件
  emit('change', scale, scale1)
  if (!props.reverseScalingIds.value) {
    return
  }
  let reverseScalingIdNames = []
  if (typeof props.reverseScalingIds.value === 'string') {
    reverseScalingIdNames.push(props.reverseScalingIds.value)
  } else {
    reverseScalingIdNames = props.reverseScalingIds.value
  }

  reverseScalingIdNames.forEach(id => {
    const element = document.querySelector(`#${id}`)
    if (!element) {
      return
    }
    /*
    * 将地图容器按照全局的scale再还原回去,位置偏移问题解决,但是地图显示较大
  获取地图宽度,根据scale计算地图当前实际应该显示的宽高像素值,直接修改地图的宽高属性。
  即修改地图真实的宽高,来适配其他部分scale之后的大小。
  * */
    let w = props.width.value * scale
    let h = props.height.value * scale

    if (String(scale).split(',').length === 2) {
      w = props.width.value * scale.split(',')[0]
      h = props.height.value * scale.split(',')[1]
    }
    element.style.setProperty('width', `${w}px`)
    element.style.setProperty('height', `${h}px`)
    element.style.setProperty('transform', `scale(${scale1})`)
    element.style.setProperty('transform-origin', '0 0')


  })


}

const fn = debounce(setScale, props.delay)

watch(() => props, () => {
  fn()
}, {deep: true})

onMounted(() => {
  setScale();
  // 添加窗口改变事件
  window.addEventListener('resize', fn)

});

onBeforeUnmount(() => {
  // 移除窗口改变事件
  window.removeEventListener('resize', fn)
})
</script>

<style scoped>

.auto-scale-box {
  position: absolute;
  transform-origin: 0 0;
  left: 0;
  top: 0;
  /*移除动画*/
  /*transition: 0.4s;*/
}


</style>
<template>
  <div
      ref="autoScaleBox"
      class="auto-scale-box"
      :style="style">
    <slot></slot>
  </div>
</template>

<script setup>

import {computed, nextTick, onBeforeUnmount, onMounted, ref, toRefs, watch} from "vue";

import {debounce} from './debounce.js';

const autoScaleBox = ref(null);

const defaultProps = defineProps({
     /**
     * 设计稿宽度
     */
    width: {
        type: [Number, String],
        default: 1920,
    },
    /**
     * 设计稿高度
     */
    height: {
        type: [Number, String],
        default: 1080,
    },
    /**
     * 缩放类型 1: 强制铺满 2: 按比例缩放 3: 适应宽度 4: 适应高度 默认值为1
     */
    scaleType: {
        type: Number,
        default: 1,
        validator(value) {
            // The value must match one of these strings
            return [1, 2, 3, 4].includes(value)
        }
    },
    /**
     * 自适应缩放防抖延迟时间(ms) 默认 100
     */
    delay: {
        type: Number,
        default: 100,
    },
    /**
     * 不被缩放的元素id
     */
    reverseScalingIds: {
        type: [Array, String],
        default: null
    },
    /**
     * 获取宽高的时候是否使用父元素的宽高 默认false
     */
    parent: {
        type: Boolean,
        default: false
    }
});

const props = toRefs(defaultProps)

const emit = defineEmits(['change'])


const transform = ref('')
const top = ref('')

const style = computed(() => {
  const w = isNaN(props.width.value) ? props.width.value : props.width.value + 'px'
  const h = isNaN(props.height.value) ? props.height.value : props.height.value + 'px'
  return {
    transform: transform.value,/*'scale(var(--scale)) translate(-50%, -50%)'*/
    width: w,
    height: h,
    top: top.value,
  }
});

/**
 * 缩放类型
 * @returns {string[]|number[]}
 */
function getScale() {
  const designWidth = props.width.value
  const designHeight = props.height.value

  let docWidth = window.innerWidth
  let docHeight = window.innerHeight
  if (props.parent) {
    if (!autoScaleBox.value) {
      console.warn('元素未渲染');
      return [1, 1]
    }
    // 获取父元素的宽高
    const {width, height} = autoScaleBox.value.parentElement.getBoundingClientRect()
    docWidth = width
    docHeight = height
  }

  // 强制铺满
  if (props.scaleType.value === 1) {
    return [`${docWidth / designWidth},${docHeight / designHeight}`, `${designWidth / docWidth},${designHeight / docHeight}`]
  }
  // 按比例缩放
  if (props.scaleType.value === 2) {

    const wh1 = docHeight / designHeight
    const wh2 = designHeight / docHeight;
    const ww1 = docWidth / designWidth;
    const ww2 = designWidth / docWidth;
    return ww1 < wh1 ? [ww1, ww2] : [wh1, wh2];
  }

  // 适应宽度
  if (props.scaleType.value === 3) {
    return [docWidth / designWidth, designWidth / docWidth]

  }

  // 适应高度
  if (props.scaleType.value === 4) {
    return [docHeight / designHeight, designHeight / docHeight]

  }


  return [1, 1]
}

function setScale() {

  let [scale, scale1] = getScale()
  transform.value = `scale(${scale})`

  //  如果是按比列适配则设置top
  if (props.scaleType.value === 2) {
    nextTick(() => {
      setTimeout(() => {
        top.value = (autoScaleBox.value.parentElement.getBoundingClientRect().height - autoScaleBox.value.getBoundingClientRect().height) / 2 + 'px'
      }, 400)
    })
  } else {
    top.value = '0'
  }
  // 将缩放比例传给父组件
  emit('change', scale, scale1)
  if (!props.reverseScalingIds.value) {
    return
  }
  let reverseScalingIdNames = []
  if (typeof props.reverseScalingIds.value === 'string') {
    reverseScalingIdNames.push(props.reverseScalingIds.value)
  } else {
    reverseScalingIdNames = props.reverseScalingIds.value
  }

  reverseScalingIdNames.forEach(id => {
    const element = document.querySelector(`#${id}`)
    if (!element) {
      return
    }
    /*
    * 将地图容器按照全局的scale再还原回去,位置偏移问题解决,但是地图显示较大
  获取地图宽度,根据scale计算地图当前实际应该显示的宽高像素值,直接修改地图的宽高属性。
  即修改地图真实的宽高,来适配其他部分scale之后的大小。
  * */
    let w = props.width.value * scale
    let h = props.height.value * scale

    if (String(scale).split(',').length === 2) {
      w = props.width.value * scale.split(',')[0]
      h = props.height.value * scale.split(',')[1]
    }
    element.style.setProperty('width', `${w}px`)
    element.style.setProperty('height', `${h}px`)
    element.style.setProperty('transform', `scale(${scale1})`)
    element.style.setProperty('transform-origin', '0 0')


  })


}

const fn = debounce(setScale, props.delay)

watch(() => props, () => {
  fn()
}, {deep: true})

onMounted(() => {
  setScale();
  // 添加窗口改变事件
  window.addEventListener('resize', fn)

});

onBeforeUnmount(() => {
  // 移除窗口改变事件
  window.removeEventListener('resize', fn)
})
</script>

<style scoped>

.auto-scale-box {
  position: absolute;
  transform-origin: 0 0;
  left: 0;
  top: 0;
  /*移除动画*/
  /*transition: 0.4s;*/
}


</style>
js
/**
 * 防抖函数
 * @param fn 执行的函数
 * @param delay 延迟时间
 * @returns {(function(): void)|*}
 */
export function debounce(fn, delay) {
    let timer;
    return function () {
        const th = this;
        const args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(function () {
            timer = null;
            fn.apply(th, args);
        }, delay);
    };
}
/**
 * 防抖函数
 * @param fn 执行的函数
 * @param delay 延迟时间
 * @returns {(function(): void)|*}
 */
export function debounce(fn, delay) {
    let timer;
    return function () {
        const th = this;
        const args = arguments;
        if (timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(function () {
            timer = null;
            fn.apply(th, args);
        }, delay);
    };
}
AI助手