面试题答案
一键面试实现思路
- 数据结构设计:
- 使用
Stack
来管理卡片的层级关系。栈顶元素为最上层的卡片,依次向下。 - 为每个卡片定义一个数据结构,包含其位置、大小、旋转角度、缩放比例等属性,方便进行动画控制。
- 使用
- 交互处理:
- 监听卡片的拖动事件,根据拖动的距离和方向更新卡片的位置。
- 当卡片被拖动到一定位置时,遍历栈中其他卡片,根据预设规则触发相应的动画响应,如缩放和旋转。
- 性能优化:
- 减少重绘和回流:批量更新卡片的属性,而不是每次小变动都触发重绘。例如,在一个动画帧内,先计算好所有卡片的新位置、缩放和旋转等属性,然后一次性更新DOM样式。
- 使用
requestAnimationFrame
:在处理动画时,利用requestAnimationFrame
来确保动画在浏览器的最佳时机进行渲染,提高动画的流畅性。 - 事件委托:将拖动事件绑定在包含所有卡片的父元素上,通过事件.target 来判断具体是哪个卡片被拖动,减少事件绑定的数量,提高性能。
- 内存管理:
- 避免内存泄漏:在卡片被移除(如从栈中弹出)时,取消相关的事件监听和动画定时器,防止引用循环导致的内存泄漏。
- 对象复用:尽量复用已有的卡片对象,而不是频繁创建和销毁。例如,当一个卡片被隐藏后,将其属性重置并重新放入栈中,而不是创建一个全新的卡片。
- 用户体验:
- 流畅的动画:确保动画的过渡平滑,通过合理设置动画的时间和缓动函数来实现。
- 实时反馈:在卡片被拖动时,即时更新其位置,让用户感受到操作的实时响应。
- 清晰的视觉效果:通过合适的样式设计,如透明度、阴影等,来突出卡片之间的遮挡关系和层级。
优化策略
- 硬件加速:对于需要进行动画的卡片,通过设置
transform
属性来利用浏览器的硬件加速,提高动画性能。例如,element.style.transform = 'translate(' + newX + 'px, ' + newY + 'px) rotate(' + rotation + 'deg) scale(' + scale + ')';
- 预加载资源:如果卡片中包含图片等资源,提前进行预加载,避免在动画过程中出现资源加载延迟导致的卡顿。
- 渐进式渲染:在页面加载时,先渲染关键的卡片,然后逐步渲染其他卡片,提高页面的初始加载速度。
关键代码示例
以下是使用JavaScript和HTML实现的关键代码示例:
HTML部分
<div id="card-container">
<!-- 卡片会通过JavaScript动态添加 -->
</div>
JavaScript部分
class Card {
constructor(id, x, y, width, height) {
this.id = id;
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.rotation = 0;
this.scale = 1;
this.createElement();
}
createElement() {
const card = document.createElement('div');
card.id = `card-${this.id}`;
card.style.position = 'absolute';
card.style.left = this.x + 'px';
card.style.top = this.y + 'px';
card.style.width = this.width + 'px';
card.style.height = this.height + 'px';
card.style.backgroundColor = 'rgba(' + (Math.random() * 255 | 0) + ', ' + (Math.random() * 255 | 0) + ', ' + (Math.random() * 255 | 0) + ', 0.8)';
card.style.zIndex = 1;
document.getElementById('card-container').appendChild(card);
}
updateStyle() {
const card = document.getElementById(`card-${this.id}`);
card.style.transform = `translate(${this.x}px, ${this.y}px) rotate(${this.rotation}deg) scale(${this.scale})`;
}
}
class CardStack {
constructor() {
this.stack = [];
this.initEventListeners();
}
addCard(card) {
this.stack.push(card);
card.updateStyle();
this.updateZIndex();
}
removeCard(card) {
const index = this.stack.indexOf(card);
if (index!== -1) {
this.stack.splice(index, 1);
this.updateZIndex();
}
}
updateZIndex() {
this.stack.forEach((card, index) => {
const cardElement = document.getElementById(`card-${card.id}`);
cardElement.style.zIndex = index + 1;
});
}
initEventListeners() {
const container = document.getElementById('card-container');
let isDragging = false;
let startX, startY;
let currentCard;
container.addEventListener('mousedown', (e) => {
const targetCard = this.stack.find(card => {
const cardElement = document.getElementById(`card-${card.id}`);
return e.clientX >= card.x && e.clientX <= card.x + card.width && e.clientY >= card.y && e.clientY <= card.y + card.height;
});
if (targetCard) {
isDragging = true;
currentCard = targetCard;
startX = e.clientX;
startY = e.clientY;
}
});
document.addEventListener('mousemove', (e) => {
if (isDragging) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
currentCard.x += dx;
currentCard.y += dy;
currentCard.updateStyle();
startX = e.clientX;
startY = e.clientY;
// 检查是否达到触发其他卡片动画的位置
this.stack.forEach(otherCard => {
if (otherCard!== currentCard) {
// 这里简单示例:当拖动的卡片中心X超过某个值,其他卡片旋转
if (currentCard.x + currentCard.width / 2 > 200) {
otherCard.rotation += 5;
otherCard.updateStyle();
}
}
});
}
});
document.addEventListener('mouseup', () => {
isDragging = false;
});
}
}
// 使用示例
const stack = new CardStack();
stack.addCard(new Card(1, 100, 100, 200, 150));
stack.addCard(new Card(2, 150, 150, 200, 150));
以上代码实现了一个简单的卡片栈,包含卡片的添加、拖动以及触发其他卡片动画的功能,同时考虑了性能优化、内存管理和用户体验方面的要点。实际应用中可根据具体需求进一步扩展和优化。