Arrows
I do not remember where I first saw this effect but it has been stuck in my head for a while. I finally decided to build it for fun. The idea is simple. Place a bunch of arrows on the screen then rotate each one so it always points toward the mouse cursor.
To do this you need to track the mouse position, get the position of each arrow and calculate the angle between those two points. Once you have the angle you rotate the arrow.
The rotation logic looks like this.
import { const ArrowRight: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>ArrowRight, const ArrowUp: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>ArrowUp, const Minus: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>Minus, const MoveRight: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>MoveRight } from "lucide-react";
import { function useState<S>(initialState: S | (() => S)): [S, React.Dispatch<React.SetStateAction<S>>] (+1 overload)Returns a stateful value, and a function to update it.useState, type interface FunctionComponent<P = {}>Represents the type of a function component. Can optionally
receive a type argument that represents the props the component
accepts.FunctionComponent, function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.useEffect, function useRef<T>(initialValue: T): React.RefObject<T> (+2 overloads)`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
(`initialValue`). The returned object will persist for the full lifetime of the component.
Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
value around similar to how you’d use instance fields in classes.useRef } from "react";
import React from "react";
const const Arrow: ({ x, y }: {
x: number;
y: number;
}) => React.JSX.Element
Arrow = ({ x: numberx, y: numbery }: { x: numberx: number; y: numbery: number }) => {
const const ref: React.RefObject<HTMLDivElement | null>ref = useRef<HTMLDivElement>(initialValue: HTMLDivElement | null): React.RefObject<HTMLDivElement | null> (+2 overloads)`useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument
(`initialValue`). The returned object will persist for the full lifetime of the component.
Note that `useRef()` is useful for more than the `ref` attribute. It’s handy for keeping any mutable
value around similar to how you’d use instance fields in classes.useRef<HTMLDivElement>(null);
const [const pos: {
x: number;
y: number;
}
pos, const setPos: React.Dispatch<React.SetStateAction<{
x: number;
y: number;
}>>
setPos] = useState<{
x: number;
y: number;
}>(initialState: {
x: number;
y: number;
} | (() => {
x: number;
y: number;
})): [{
x: number;
y: number;
}, React.Dispatch<React.SetStateAction<{
x: number;
y: number;
}>>] (+1 overload)
Returns a stateful value, and a function to update it.useState({ x: numberx: 0, y: numbery: 0 });
const [const degrees: numberdegrees, const setDegrees: React.Dispatch<React.SetStateAction<number>>setDegrees] = useState<number>(initialState: number | (() => number)): [number, React.Dispatch<React.SetStateAction<number>>] (+1 overload)Returns a stateful value, and a function to update it.useState(0);
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.useEffect(() => {
const const updatePos: () => voidupdatePos = () => {
if (!const ref: React.RefObject<HTMLDivElement | null>ref.RefObject<HTMLDivElement | null>.current: HTMLDivElement | nullThe current value of the ref.current) return;
const const bounds: DOMRectbounds = const ref: React.RefObject<HTMLDivElement | null>ref.RefObject<HTMLDivElement | null>.current: HTMLDivElementThe current value of the ref.current.Element.getBoundingClientRect(): DOMRectThe **`Element.getBoundingClientRect()`** method returns a position relative to the viewport.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Element/getBoundingClientRect)getBoundingClientRect();
const setPos: (value: React.SetStateAction<{
x: number;
y: number;
}>) => void
setPos({
x: numberx: const bounds: DOMRectbounds.DOMRect.x: numberThe **`x`** property of the DOMRect interface represents the x-coordinate of the rectangle, which is the horizontal distance between the viewport's left edge and the rectangle's origin.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMRect/x)x + const bounds: DOMRectbounds.DOMRect.width: numberThe **`width`** property of the DOMRect interface represents the width of the rectangle.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMRect/width)width / 2,
y: numbery: const bounds: DOMRectbounds.DOMRect.y: numberThe **`y`** property of the DOMRect interface represents the y-coordinate of the rectangle, which is the vertical distance between the viewport's top edge and the rectangle's origin.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMRect/y)y + const bounds: DOMRectbounds.DOMRect.height: numberThe **`height`** property of the DOMRect interface represents the height of the rectangle.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMRect/height)height / 2,
});
};
const updatePos: () => voidupdatePos();
var window: Window & typeof globalThisThe **`window`** property of a Window object points to the window object itself.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)window.addEventListener<"resize">(type: "resize", listener: (this: Window, ev: UIEvent) => any, options?: boolean | AddEventListenerOptions): void (+1 overload)The **`addEventListener()`** method of the EventTarget interface sets up a function that will be called whenever the specified event is delivered to the target.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener)addEventListener("resize", const updatePos: () => voidupdatePos);
return () => var window: Window & typeof globalThisThe **`window`** property of a Window object points to the window object itself.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/window)window.removeEventListener<"resize">(type: "resize", listener: (this: Window, ev: UIEvent) => any, options?: boolean | EventListenerOptions): void (+1 overload)The **`removeEventListener()`** method of the EventTarget interface removes an event listener previously registered with EventTarget.addEventListener() from the target.
[MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener)removeEventListener("resize", const updatePos: () => voidupdatePos);
}, []);
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): voidAccepts a function that contains imperative, possibly effectful code.useEffect(() => {
const const deltaX: numberdeltaX = x: numberx - const pos: {
x: number;
y: number;
}
pos.x: numberx;
const const deltaY: numberdeltaY = y: numbery - const pos: {
x: number;
y: number;
}
pos.y: numbery;
const const angle: numberangle = var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.atan2(y: number, x: number): numberReturns the angle (in radians) between the X axis and the line going through both the origin and the given point.atan2(const deltaY: numberdeltaY, const deltaX: numberdeltaX) * (180 / var Math: MathAn intrinsic object that provides basic mathematics functionality and constants.Math.Math.PI: numberPi. This is the ratio of the circumference of a circle to its diameter.PI);
const setDegrees: (value: React.SetStateAction<number>) => voidsetDegrees(const angle: numberangle);
}, [x: numberx, y: numbery, const pos: {
x: number;
y: number;
}
pos]);
return (
<JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div
RefAttributes<HTMLDivElement>.ref?: React.Ref<HTMLDivElement> | undefinedAllows getting a ref to the component instance.
Once the component unmounts, React will set `ref.current` to `null`
(or call the ref with `null` if you passed a callback ref).ref={const ref: React.RefObject<HTMLDivElement | null>ref}
HTMLAttributes<HTMLDivElement>.style?: React.CSSProperties | undefinedstyle={{
StandardLonghandProperties<string | number, string & {}>.transform?: Property.Transform | undefinedThe **`transform`** CSS property lets you rotate, scale, skew, or translate an element. It modifies the coordinate space of the CSS visual formatting model.
**Syntax**: `none | <transform-list>`
**Initial value**: `none`
| Chrome | Firefox | Safari | Edge | IE |
| :-----: | :-----: | :-------: | :----: | :-----: |
| **36** | **16** | **9** | **12** | **10** |
| 1 _-x-_ | | 3.1 _-x-_ | | 9 _-x-_ |transform: `translate(-50%, -50%) rotate(${const degrees: numberdegrees}deg)`,
}}
HTMLAttributes<HTMLDivElement>.className?: string | undefinedclassName="w-full aspect-square grid place-items-center"
>
<const Minus: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>Minus />
</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div>
);
};
The angle between any two points can be calculated using:
Here is the important part of the code again.
useEffect(() => {
const deltaX = x - pos.x;
const deltaY = y - pos.y;
const angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
setDegrees(angle);
}, [x, y, pos]);
First you get the difference between the cursor position and the arrow position. Then you pass those values to Math.atan2 which gives the angle in radians. Since CSS transforms use degrees you convert it by multiplying with 180/π. That’s pretty much it.