面试题答案
一键面试1. 实现React组件基础结构
首先,创建一个React组件。假设组件名为CircleMouseTrail
。
import React, { useState, useEffect } from'react';
const CircleMouseTrail = () => {
const [isDrawingCircle, setIsDrawingCircle] = useState(false);
const [startX, setStartX] = useState(0);
const [startY, setStartY] = useState(0);
const [prevX, setPrevX] = useState(0);
const [prevY, setPrevY] = useState(0);
const [radius, setRadius] = useState(0);
return (
<div
style={{
width: '100vw',
height: '100vh',
border: '1px solid black',
position:'relative'
}}
onMouseDown={(e) => {
setIsDrawingCircle(true);
setStartX(e.clientX);
setStartY(e.clientY);
setPrevX(e.clientX);
setPrevY(e.clientY);
}}
onMouseMove={(e) => {
if (isDrawingCircle) {
// 计算当前点到起始点的距离
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newRadius = Math.sqrt(dx * dx + dy * dy);
setRadius(newRadius);
// 这里还需要进一步判断是否是顺时针移动
// 例如通过计算角度变化来判断
// 简单的通过叉积判断方向(假设顺时针为正方向)
const crossProduct = (e.clientX - prevX) * (prevY - startY) - (prevX - startX) * (e.clientY - prevY);
if (crossProduct > 0) {
// 这里可以添加更多逻辑判断是否真的是在画圆
// 例如半径变化不能太大等
// 假设已经确定是在顺时针画圆,触发动画
// 这里简单设置一个变量表示满足条件,实际可触发动画
setIsDrawingCircle(true);
} else {
setIsDrawingCircle(false);
}
setPrevX(e.clientX);
setPrevY(e.clientY);
}
}}
onMouseUp={() => {
setIsDrawingCircle(false);
}}
>
{/* 这里可以添加动画相关元素 */}
</div>
);
};
export default CircleMouseTrail;
2. 检测鼠标轨迹
- 起始点记录:在
onMouseDown
事件中,记录鼠标按下时的坐标startX
和startY
,作为画圆的起始点。 - 移动过程记录:在
onMouseMove
事件中,计算当前鼠标位置与起始点的距离radius
,同时记录上一个鼠标位置prevX
和prevY
。 - 判断顺时针方向:通过计算相邻两个向量的叉积来判断鼠标移动方向是否为顺时针。若叉积大于0,则认为是顺时针方向。同时可以添加一些半径变化的限制条件,以确保确实是在画圆而不是随意移动。
3. 处理动画触发逻辑
- 状态管理:使用
isDrawingCircle
状态变量来表示当前是否处于符合条件的画圆状态。当检测到鼠标以顺时针画圆时,设置isDrawingCircle
为true
。 - 触发动画:在实际应用中,可以根据
isDrawingCircle
的值来触发CSS动画或使用React动画库(如react - spring
、framer - motion
等)来实现特定的动画效果。例如,如果使用CSS动画,可以在组件内添加一个元素,并根据isDrawingCircle
添加或移除对应的动画类名。
import React, { useState, useEffect } from'react';
const CircleMouseTrail = () => {
const [isDrawingCircle, setIsDrawingCircle] = useState(false);
const [startX, setStartX] = useState(0);
const [startY, setStartY] = useState(0);
const [prevX, setPrevX] = useState(0);
const [prevY, setPrevY] = useState(0);
const [radius, setRadius] = useState(0);
return (
<div
style={{
width: '100vw',
height: '100vh',
border: '1px solid black',
position:'relative'
}}
onMouseDown={(e) => {
setIsDrawingCircle(true);
setStartX(e.clientX);
setStartY(e.clientY);
setPrevX(e.clientX);
setPrevY(e.clientY);
}}
onMouseMove={(e) => {
if (isDrawingCircle) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newRadius = Math.sqrt(dx * dx + dy * dy);
setRadius(newRadius);
const crossProduct = (e.clientX - prevX) * (prevY - startY) - (prevX - startX) * (e.clientY - prevY);
if (crossProduct > 0) {
setIsDrawingCircle(true);
} else {
setIsDrawingCircle(false);
}
setPrevX(e.clientX);
setPrevY(e.clientY);
}
}}
onMouseUp={() => {
setIsDrawingCircle(false);
}}
>
<div
style={{
width: '50px',
height: '50px',
backgroundColor: 'blue',
borderRadius: '50%',
position: 'absolute',
animation: isDrawingCircle? 'circleAnimation 2s ease - in - out forwards' : 'none'
}}
/>
<style>{`
@keyframes circleAnimation {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
`}</style>
</div>
);
};
export default CircleMouseTrail;
4. 兼容性问题及解决方案
- 事件兼容性:
onMouseDown
、onMouseMove
和onMouseUp
在大多数现代浏览器中都有较好的支持,但在一些古老浏览器(如IE)中可能存在问题。可以使用addEventListener
进行事件绑定的polyfill来解决。例如,使用@types/jquery
库来简化事件绑定,或者自己编写兼容代码。
import React, { useState, useEffect } from'react';
const CircleMouseTrail = () => {
const [isDrawingCircle, setIsDrawingCircle] = useState(false);
const [startX, setStartX] = useState(0);
const [startY, setStartY] = useState(0);
const [prevX, setPrevX] = useState(0);
const [prevY, setPrevY] = useState(0);
const [radius, setRadius] = useState(0);
useEffect(() => {
const handleMouseDown = (e: MouseEvent) => {
setIsDrawingCircle(true);
setStartX(e.clientX);
setStartY(e.clientY);
setPrevX(e.clientX);
setPrevY(e.clientY);
};
const handleMouseMove = (e: MouseEvent) => {
if (isDrawingCircle) {
const dx = e.clientX - startX;
const dy = e.clientY - startY;
const newRadius = Math.sqrt(dx * dx + dy * dy);
setRadius(newRadius);
const crossProduct = (e.clientX - prevX) * (prevY - startY) - (prevX - startX) * (e.clientY - prevY);
if (crossProduct > 0) {
setIsDrawingCircle(true);
} else {
setIsDrawingCircle(false);
}
setPrevX(e.clientX);
setPrevY(e.clientY);
}
};
const handleMouseUp = () => {
setIsDrawingCircle(false);
};
const target = document.querySelector('div');
if (target) {
target.addEventListener('mousedown', handleMouseDown);
target.addEventListener('mousemove', handleMouseMove);
target.addEventListener('mouseup', handleMouseUp);
}
return () => {
if (target) {
target.removeEventListener('mousedown', handleMouseDown);
target.removeEventListener('mousemove', handleMouseMove);
target.removeEventListener('mouseup', handleMouseUp);
}
};
}, [isDrawingCircle]);
return (
<div
style={{
width: '100vw',
height: '100vh',
border: '1px solid black',
position:'relative'
}}
>
<div
style={{
width: '50px',
height: '50px',
backgroundColor: 'blue',
borderRadius: '50%',
position: 'absolute',
animation: isDrawingCircle? 'circleAnimation 2s ease - in - out forwards' : 'none'
}}
/>
<style>{`
@keyframes circleAnimation {
from {
transform: scale(0);
}
to {
transform: scale(1);
}
}
`}</style>
</div>
);
};
export default CircleMouseTrail;
- CSS动画兼容性:不同浏览器对CSS动画的支持可能存在差异,尤其是一些旧版本浏览器。可以使用Autoprefixer工具,在构建过程中自动添加浏览器前缀(如
-webkit -
、-moz -
、-ms -
等),确保动画在各种浏览器中正常显示。例如,在Webpack配置中添加postcss - loader
和autoprefixer
插件。
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
'style - loader',
{
loader: 'css - loader',
options: {
importLoaders: 1
}
},
{
loader: 'postcss - loader',
options: {
postcssOptions: {
plugins: [
require('autoprefixer')
]
}
}
}
]
}
]
}
};