React TS中带有遮罩图像的径向动画聚焦效果



我正在用掩码图像重新创建这个Radial动画聚焦效果:Codepen我知道我可以复制&将CSS粘贴到.CSS文件中,但我希望通过样式化的组件实现相同的结果。为此,我在我的样式组件中声明了CSS并应用了它。但我不确定为什么什么都没有发生,我应该使用而不是getElementById,因为手动DOM操作是一种糟糕的做法?

应用程序.tsx

import React from "react";
import styled from "styled-components";
const Property = styled.div`
@property --focal-size {
syntax: "<length-percentage>";
initial-value: 100%;
inherits: false;
}
`;
const FocusZoom = styled.div`
--mouse-x: center;
--mouse-y: center;
--backdrop-color: hsl(200 50% 0% / 50%); /* can't be opaque */
--backdrop-blur-strength: 10px;

position: fixed;
touch-action: none;
inset: 0;
background-color: var(--backdrop-color);
backdrop-filter: blur(var(--backdrop-blur-strength));

mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y), 
transparent var(--focal-size), 
black 0%
);

transition: --focal-size .3s ease;

/*  debug/grok the gradient mask image here   */
/*   background-image: radial-gradient(
circle, 
transparent 100px, 
black 0%
); */
}
`;
function App(bool: boolean) {
const zoom: Element = document.querySelector("focus-zoom");
const toggleSpotlight = (bool) =>
zoom.style.setProperty("--focal-size", bool ? "15vmax" : "100%");
window.addEventListener("pointermove", (e) => {
zoom.style.setProperty("--mouse-x", e.clientX + "px");
zoom.style.setProperty("--mouse-y", e.clientY + "px");
});
window.addEventListener("keydown", (e) => toggleSpotlight(e.altKey));
window.addEventListener("keyup", (e) => toggleSpotlight(e.altKey));
window.addEventListener("touchstart", (e) => toggleSpotlight(true));
window.addEventListener("touchend", (e) => toggleSpotlight(false));
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom></FocusZoom>
</>
);
}
export default App;

查看带有样式组件的解决方案代码沙盒

import React, { useEffect } from "react";
import styled, { createGlobalStyle } from "styled-components";
export const GlobalStyle = createGlobalStyle`
body {
display: flex;
align-items: center;
justify-content: center;
}
/* custom properties */
:root {  
--focal-size: { 
syntax: "<length-percentage>";
initial-value: 100%;
inherits: false;
}
--mouse-x: center;
--mouse-y: center;
--backdrop-color: hsl(200 50% 0% / 50%);
--backdrop-blur-strength: 10px;
}
`;
const Wrapper = styled.div`
height: 400px;
width: 400px;
background: conic-gradient(
from -0.5turn at bottom right,
deeppink,
cyan,
rebeccapurple
);
`;
const FocusZoom = styled.div`
position: fixed;
touch-action: none;
inset: 0;
background-color: var(--backdrop-color);
backdrop-filter: blur(var(--backdrop-blur-strength));
mask-image: radial-gradient(
circle at var(--mouse-x) var(--mouse-y),
transparent var(--focal-size),
black 0%
);
transition: --focal-size 0.3s ease;
`;
function App(bool) {
useEffect(() => {
const zoom = document.getElementById("zoomId");
const toggleSpotlight = (bool) =>
zoom.style.setProperty("--focal-size", bool ? "15vmax" : "100%");
window.addEventListener("pointermove", (e) => {
zoom.style.setProperty("--mouse-x", e.clientX + "px");
zoom.style.setProperty("--mouse-y", e.clientY + "px");
});
window.addEventListener("keydown", (e) => toggleSpotlight(e.altKey));
window.addEventListener("keyup", (e) => toggleSpotlight(e.altKey));
window.addEventListener("touchstart", (e) => toggleSpotlight(true));
window.addEventListener("touchend", (e) => toggleSpotlight(false));
toggleSpotlight();
}, []);
return (
<Wrapper>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom id="zoomId"></FocusZoom>
</Wrapper>
);
}
export default App;

此外,确保您拥有全局样式&在应用程序文件中导入的组件。

import Test, { GlobalStyle } from "./test";
export default function App() {
return (
<div className="App">
<GlobalStyle />
<Test />
</div>
);
}

正如其他人所提到的,我们可以通过使用useRef钩子来简单地引用React组件模板中的DOM元素:

function App() {
// Get an imperative reference to a DOM element
const zoomRef = useRef<HTMLDivElement>(null);
const toggleSpotlight = (bool: boolean) =>
// To get the DOM element, use the .current property of the ref
zoomRef.current?.style.setProperty(
"--focal-size",
bool ? "15vmax" : "100%"
);
// Etc. including event listeners
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
<FocusZoom ref={zoomRef} /> {/* Pass the reference to the special ref prop */}
</>
);
}

演示:https://codesandbox.io/s/exciting-flower-349b48?file=/src/App.tsx


一个更密集的解决方案可以利用样式组件道具自适应来取代对zoom.style.setProperty()的调用,如React中的Jumping Text中所述,使用样式组件

特别是,这可以帮助取代CSS变量的使用。

不幸的是,除了配置了转换的--focal-size

const FocusZoom = styled.div<{
focalSize: string; // Specify the extra styling props for adaptation
pointerPos: { x: string; y: string };
}>`
--focal-size: ${(props) => props.focalSize};
position: fixed;
touch-action: none;
inset: 0;
background-color: hsl(200 50% 0% / 50%);
backdrop-filter: blur(10px);
mask-image: radial-gradient(
circle at ${(props) => props.pointerPos.x + " " + props.pointerPos.y},
transparent var(--focal-size),
black 0%
);
transition: --focal-size 0.3s ease;
`;
function App() {
// Store all dynamic values into state
const [focalSize, setFocalSize] = useState("100%");
const [pointerPosition, setPointerPosition] = useState({
x: "center",
y: "center"
});
const toggleSpotlight = (bool: boolean) =>
// Change the state instead of messing directly with the DOM element
setFocalSize(bool ? "15vmax" : "100%");
// Etc. including event listeners
return (
<>
<h1>
Press <kbd>Opt/Alt</kbd> or touch for a spotlight effect
</h1>
{/* Pass the states to the styled component */}
<FocusZoom focalSize={focalSize} pointerPos={pointerPosition} />
</>
);
}

演示:https://codesandbox.io/s/frosty-swirles-jdbcte?file=/src/App.tsx

对于这种值一直在变化(尤其是鼠标位置(的情况,这种解决方案可能有些过头了,但它将逻辑与样式实现解耦(组件不知道是否使用了CSS变量(。


旁注:对于事件侦听器,请确保只附加它们一次(通常使用具有空依赖数组的useEffect(cb, [])(,并在组件卸载时删除它们(通常通过从useEffect回调返回清理函数(。

例如,您也可以使用react-use中的useEvent,它直接包含以下内容:

React为事件订阅handler的传感器挂钩。

import { useEvent } from "react-use";
function App() {
// Attaches to window and takes care of removing on unmount
useEvent("pointermove", (e: PointerEvent) =>
setPointerPosition({ x: e.clientX + "px", y: e.clientY + "px" })
);
// Etc.
}

您应该使用useRef钩子而不是getElementById

最新更新