MST

星途 面试题库

面试题:Vue拖拽功能优化之性能提升,如何避免频繁重绘和回流?

在实现Vue的拖拽功能后,发现页面性能有所下降,经分析是由于频繁的重绘和回流导致。请阐述你会采取哪些措施来优化这一情况,例如如何合理地更新元素的位置,是否可以使用CSS的某些属性或Vue的特性来减少重绘和回流的次数,给出具体的思路和可能用到的代码片段。
15.6万 热度难度
前端开发Vue

知识考点

AI 面试

面试题答案

一键面试

优化思路

  1. 使用 transform 属性
    • 原因:transform 不会触发重排和重绘,而是创建一个新的合成层,通过改变 transformtranslateXtranslateY 值来移动元素位置,能极大减少性能损耗。
    • 思路:在拖拽过程中,监听拖拽事件获取偏移量,然后将偏移量应用到 transform 属性上。
  2. 防抖与节流
    • 原因:拖拽过程中事件触发频率很高,防抖和节流可以限制事件触发的频率,从而减少不必要的计算和重绘回流。
    • 思路:
      • 防抖:设置一个延迟时间,在延迟时间内如果事件再次触发,则重新计时,延迟时间结束后才执行实际的更新操作。
      • 节流:规定在一定时间间隔内,只能触发一次更新操作,无论拖拽事件触发多么频繁。
  3. 使用 requestAnimationFrame
    • 原因:requestAnimationFrame 会在浏览器下一次重绘之前执行回调函数,它会自动优化执行时机,保证动画的流畅性,避免在每一帧中不必要的重绘回流。
    • 思路:在拖拽事件触发时,使用 requestAnimationFrame 来批量处理元素位置更新。
  4. Vue 的 will-change 特性
    • 原因:will-change CSS 属性告知浏览器该元素即将发生变化,浏览器可以提前优化相关资源,为即将到来的变化做好准备,从而减少重绘回流的性能损耗。
    • 思路:在元素拖拽前,设置 will-change: transformwill-change: left, top 等相关属性,告知浏览器即将改变的属性。

代码示例

  1. 使用 transform 属性
<template>
  <div
    ref="draggable"
    :style="{ transform: `translateX(${dragX}px) translateY(${dragY}px)` }"
    @mousedown="startDrag"
  >
    Draggable Element
  </div>
</template>

<script>
export default {
  data() {
    return {
      dragX: 0,
      dragY: 0,
      isDragging: false,
      startX: 0,
      startY: 0
    };
  },
  methods: {
    startDrag(e) {
      this.isDragging = true;
      this.startX = e.clientX;
      this.startY = e.clientY;
      document.addEventListener('mousemove', this.drag);
      document.addEventListener('mouseup', this.stopDrag);
    },
    drag(e) {
      if (this.isDragging) {
        this.dragX += e.clientX - this.startX;
        this.dragY += e.clientY - this.startY;
        this.startX = e.clientX;
        this.startY = e.clientY;
      }
    },
    stopDrag() {
      this.isDragging = false;
      document.removeEventListener('mousemove', this.drag);
      document.removeEventListener('mouseup', this.stopDrag);
    }
  }
};
</script>
  1. 防抖示例
// 防抖函数
function debounce(func, delay) {
  let timer;
  return function() {
    const context = this;
    const args = arguments;
    clearTimeout(timer);
    timer = setTimeout(() => {
      func.apply(context, args);
    }, delay);
  };
}

export default {
  data() {
    return {
      // 其他数据...
    };
  },
  methods: {
    dragDebounced: debounce(function(e) {
      if (this.isDragging) {
        // 处理拖拽逻辑,更新位置
      }
    }, 100)
  },
  // 其他生命周期和方法...
};
  1. 节流示例
// 节流函数
function throttle(func, interval) {
  let lastTime = 0;
  return function() {
    const now = new Date().getTime();
    const context = this;
    const args = arguments;
    if (now - lastTime >= interval) {
      func.apply(context, args);
      lastTime = now;
    }
  };
}

export default {
  data() {
    return {
      // 其他数据...
    };
  },
  methods: {
    dragThrottled: throttle(function(e) {
      if (this.isDragging) {
        // 处理拖拽逻辑,更新位置
      }
    }, 100)
  },
  // 其他生命周期和方法...
};
  1. 使用 requestAnimationFrame
<template>
  <div
    ref="draggable"
    :style="{ transform: `translateX(${dragX}px) translateY(${dragY}px)` }"
    @mousedown="startDrag"
  >
    Draggable Element
  </div>
</template>

<script>
export default {
  data() {
    return {
      dragX: 0,
      dragY: 0,
      isDragging: false,
      startX: 0,
      startY: 0,
      frameRequest: null
    };
  },
  methods: {
    startDrag(e) {
      this.isDragging = true;
      this.startX = e.clientX;
      this.startY = e.clientY;
      document.addEventListener('mousemove', this.drag);
      document.addEventListener('mouseup', this.stopDrag);
    },
    drag(e) {
      if (this.isDragging) {
        if (this.frameRequest) {
          cancelAnimationFrame(this.frameRequest);
        }
        const newX = this.dragX + e.clientX - this.startX;
        const newY = this.dragY + e.clientY - this.startY;
        this.frameRequest = requestAnimationFrame(() => {
          this.dragX = newX;
          this.dragY = newY;
          this.startX = e.clientX;
          this.startY = e.clientY;
        });
      }
    },
    stopDrag() {
      this.isDragging = false;
      if (this.frameRequest) {
        cancelAnimationFrame(this.frameRequest);
      }
      document.removeEventListener('mousemove', this.drag);
      document.removeEventListener('mouseup', this.stopDrag);
    }
  }
};
</script>
  1. Vue 的 will-change 特性
<template>
  <div
    ref="draggable"
    :style="{
      willChange: 'transform',
      transform: `translateX(${dragX}px) translateY(${dragY}px)`
    }"
    @mousedown="startDrag"
  >
    Draggable Element
  </div>
</template>

<script>
export default {
  data() {
    return {
      dragX: 0,
      dragY: 0,
      isDragging: false,
      startX: 0,
      startY: 0
    };
  },
  methods: {
    startDrag(e) {
      // 开始拖拽逻辑
    },
    // 其他拖拽相关方法...
  }
};
</script>