Arrows


x: 0y: 0

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>>
@component@nameArrowRight@descriptionLucide SVG icon component, renders SVG Element with children.@preview![img](data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNSAxMmgxNCIgLz4KICA8cGF0aCBkPSJtMTIgNSA3IDctNyA3IiAvPgo8L3N2Zz4K) - https://lucide.dev/icons/arrow-right@seehttps://lucide.dev/guide/packages/lucide-react - Documentation@paramprops - Lucide icons props and any valid SVG attribute@returnsJSX Element
ArrowRight
, const ArrowUp: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>
@component@nameArrowUp@descriptionLucide SVG icon component, renders SVG Element with children.@preview![img](data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJtNSAxMiA3LTcgNyA3IiAvPgogIDxwYXRoIGQ9Ik0xMiAxOVY1IiAvPgo8L3N2Zz4K) - https://lucide.dev/icons/arrow-up@seehttps://lucide.dev/guide/packages/lucide-react - Documentation@paramprops - Lucide icons props and any valid SVG attribute@returnsJSX Element
ArrowUp
, const Minus: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>
@component@nameMinus@descriptionLucide SVG icon component, renders SVG Element with children.@preview![img](data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNSAxMmgxNCIgLz4KPC9zdmc+Cg==) - https://lucide.dev/icons/minus@seehttps://lucide.dev/guide/packages/lucide-react - Documentation@paramprops - Lucide icons props and any valid SVG attribute@returnsJSX Element
Minus
, const MoveRight: React.ForwardRefExoticComponent<Omit<LucideProps, "ref"> & React.RefAttributes<SVGSVGElement>>
@component@nameMoveRight@descriptionLucide SVG icon component, renders SVG Element with children.@preview![img](data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNMTggOEwyMiAxMkwxOCAxNiIgLz4KICA8cGF0aCBkPSJNMiAxMkgyMiIgLz4KPC9zdmc+Cg==) - https://lucide.dev/icons/move-right@seehttps://lucide.dev/guide/packages/lucide-react - Documentation@paramprops - Lucide icons props and any valid SVG attribute@returnsJSX Element
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.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
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.
@templateP The props the component accepts.@see{@link https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components React TypeScript Cheatsheet}@example```tsx // With props: type Props = { name: string } const MyComponent: FunctionComponent<Props> = (props) => { return <div>{props.name}</div> } ```@example```tsx // Without props: const MyComponentWithoutProps: FunctionComponent = () => { return <div>MyComponentWithoutProps</div> } ```
FunctionComponent
, function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
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.
@version16.8.0@see{@link https://react.dev/reference/react/useRef}
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.
@version16.8.0@see{@link https://react.dev/reference/react/useRef}
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.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
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.
@version16.8.0@see{@link https://react.dev/reference/react/useState}
useState
(0);
function useEffect(effect: React.EffectCallback, deps?: React.DependencyList): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
useEffect
(() => {
const const updatePos: () => voidupdatePos = () => { if (!const ref: React.RefObject<HTMLDivElement | null>ref.RefObject<HTMLDivElement | null>.current: HTMLDivElement | null
The current value of the ref.
current
) return;
const const bounds: DOMRectbounds = const ref: React.RefObject<HTMLDivElement | null>ref.RefObject<HTMLDivElement | null>.current: HTMLDivElement
The current value of the ref.
current
.Element.getBoundingClientRect(): DOMRect
The **`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: number
The **`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: number
The **`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: number
The **`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: number
The **`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 globalThis
The **`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 globalThis
The **`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): void
Accepts a function that contains imperative, possibly effectful code.
@parameffect Imperative function that can return a cleanup function@paramdeps If present, effect will only activate if the values in the list change.@version16.8.0@see{@link https://react.dev/reference/react/useEffect}
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: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.atan2(y: number, x: number): number
Returns the angle (in radians) between the X axis and the line going through both the origin and the given point.
@paramy A numeric expression representing the cartesian y-coordinate.@paramx A numeric expression representing the cartesian x-coordinate.
atan2
(const deltaY: numberdeltaY, const deltaX: numberdeltaX) * (180 / var Math: Math
An intrinsic object that provides basic mathematics functionality and constants.
Math
.Math.PI: number
Pi. 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> | undefined
Allows 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).
@see{@link https://react.dev/learn/referencing-values-with-refs#refs-and-the-dom React Docs}
ref
={const ref: React.RefObject<HTMLDivElement | null>ref}
HTMLAttributes<HTMLDivElement>.style?: React.CSSProperties | undefinedstyle={{ StandardLonghandProperties<string | number, string & {}>.transform?: Property.Transform | undefined
The **`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-_ |
@seehttps://developer.mozilla.org/docs/Web/CSS/transform
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>>
@component@nameMinus@descriptionLucide SVG icon component, renders SVG Element with children.@preview![img](data:image/svg+xml;base64,PHN2ZyAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogIHdpZHRoPSIyNCIKICBoZWlnaHQ9IjI0IgogIHZpZXdCb3g9IjAgMCAyNCAyNCIKICBmaWxsPSJub25lIgogIHN0cm9rZT0iIzAwMCIgc3R5bGU9ImJhY2tncm91bmQtY29sb3I6ICNmZmY7IGJvcmRlci1yYWRpdXM6IDJweCIKICBzdHJva2Utd2lkdGg9IjIiCiAgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIgogIHN0cm9rZS1saW5lam9pbj0icm91bmQiCj4KICA8cGF0aCBkPSJNNSAxMmgxNCIgLz4KPC9zdmc+Cg==) - https://lucide.dev/icons/minus@seehttps://lucide.dev/guide/packages/lucide-react - Documentation@paramprops - Lucide icons props and any valid SVG attribute@returnsJSX Element
Minus
/>
</JSX.IntrinsicElements.div: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>div> ); };

The angle between any two points can be calculated using:

atan2(Δy,Δx)atan2(Δy, Δx)

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.

347 words

© 2023. All rights reserved.