我正在尝试解决 React 挑战。挑战是当你点击屏幕上的某个地方时,你点击的地方会出现一个圆圈。您还有一个重做和撤消按钮。问题发生在以下限制条件下:
我解决了第一个,宽度和高度等于 vw 中的相同值。 我还解决了第二个从位置减去圆宽度的一半。 但是对于最后一个,我在顶部和底部边界上遇到了问题。当屏幕宽度改变时,圆圈会移动。
这是我的代码:
App.tsx
export interface IPoint {
x: number
y: number
size: number
color: string
}
const pointSize = 5
export const App: FC = () => {
const [points, setPoints] = useState<IPoint[]>([])
const controlsRef = useRef<HTMLDivElement>(null)
const handlePlacePoint = (e: React.MouseEvent) => {
const offsetX = pointSize / 2
const offsetY = pointSize / 2
let x = (e.clientX / window.innerWidth) * 100
let y = (e.clientY / window.innerHeight) * 100
const controlsHeight =
(controlsRef.current?.offsetHeight! / window.innerHeight) * 100
if (x <= offsetX) {
x = offsetX
} else if (x >= 100 - offsetX) {
x = 100 - offsetX
}
if (y <= controlsHeight + offsetY) {
y = controlsHeight + offsetY
} else if (y >= 100 - offsetY) {
y = 100 - offsetY
}
const newPoint: IPoint = {
x,
y,
color: generateColor(),
size: pointSize
}
setPoints([...points, newPoint])
}
return (
<main>
<div className='controls' ref={controlsRef}>
<button>Undo ↺</button>
<button>Redo ↻</button>
</div>
<div
className='points-panel'
onClick={handlePlacePoint}
style={{ backgroundColor: '#8b3ae0' }}
>
{points.map(point => (
<Point key={Math.random()} {...point} />
))}
</div>
</main>
)
}
Point.tsx
export const Point: FC<IPoint> = ({ x, y, size, color }) => {
return (
<div
className='point'
style={{
backgroundColor: color,
top: `calc(${y}vh - ${size / 2}vw)`,
left: `calc(${x}vw - ${size / 2}vw)`,
width: `${size}vw`,
height: `${size}vw`
}}
/>
)
}
App.css
.controls {
position: absolute;
z-index: 1;
width: 100vw;
height: 5vh;
display: flex;
}
.controls button {
flex-grow: 1;
font-size: 25px;
}
.pointsPanel {
position: relative;
height: 100vh;
width: 100vw;
}
.point {
position: absolute;
border-radius: 100%;
border: 2px solid #000;
}
index.css
html,
body,
* {
margin: 0;
box-sizing: border-box;
}
body {
overflow: hidden;
}
我尝试了很多不同的方法,但我想我错过了 vh 中的 Point top 值减去 vw 中的值。我不知道如何改变它而不是破坏已经有效的东西。你能告诉我如何修复它以便它满足所有 3 个要求吗?或者也许有更干净的方法?
您忽略了 React 仍然只是 JavaScript,并且在具有完整 CSS 支持的浏览器中运行,从而使事情过于复杂。通过使用原生 JS 函数,并依靠 CSS 来放置东西,你需要的一堆东西可以更简单地完成:
import { useState, useRef } from "react";
import Circle from "./Circle.jsx";
import "./App.css";
export default function App() {
const field = useRef();
const [points, setPoints] = useState([]);
const placeCircle = e => {
const { offsetX, offsetY } = e.nativeEvent;
const { width, height } = field.current.getBoundingClientRect();
points.push({
id: Date.now(),
x: offsetX / width,
y: offsetY / height,
});
setPoints(points);
};
const circles = points.map(p => <Circle key={p.id} {...p} />);
return (
<main>
<div className='panel' onClick={placeCircle} ref={field}>
{circles}
</div>
</main>
);
}
原生点击事件有一个
offsetX
和 offsetY
,这正是您在此处获取 div inside 的点击位置所需要的。所以我们使用 event.nativeEvent
来获取它们,然后将它们从坐标转换为百分比,我们只需将它们除以父 div 的宽度和高度。使用每个 HTML 元素都有的 getBoundingClientRect()
函数,这在普通 JS 中又是微不足道的。
然后我们将我们的
Circle
定义为一个非常非常愚蠢的东西:
import { useState, useRef } from "react";
import "./Circle.css";
export default function Circle(props) {
const sizing = {
"--x": props.x,
"--y": props.y,
"--size": props.size ?? "1em",
};
return <div className="circle" style={sizing}/>;
}
所以是的,它什么都不做,它只是转发我们需要的值作为 CSS 变量,我们不会让 JS 在这里做anything。相反,我们让浏览器为我们做这件事:
.circle {
position: absolute;
top: calc(100% * var(--y));
left: calc(100% * var(--x));
width: var(--size);
height: var(--size);
margin-top: calc(var(--size) / -2);
margin-left: calc(var(--size) / -2);
background: red;
border: 2px solid #000;
border-radius: 100%;
}
我们完成了。