Floating UI - Coordinate System
- 文章發表於
- ...
Introduction
Ever wondered how libraries like Floating UI know exactly where to place a tooltip? It all comes down to understanding the browser's coordinate system and some clever math. In this article, we'll break down the positioning logic step by step, from basic rectangles to the complete computeCoordsFromPlacement function.
Floating UI uses the browser's coordinate system, where the origin (0, 0) is at the top-left corner of the viewport. Let's start by understanding how a single rectangle is defined:
interface Rect {x: number;y: number;width: number;height: number;}
x/y– X/Y-coordinates of the rectangle origin relative to window,width/height– width/height of the rectangle (can be negative).
As long as we know the XY coordinates and the element's height and width, we can derive the properties below.
top/bottom– Y-coordinate for the top/bottom rectangle edge,left/right– X-coordinate for the left/right rectangle edge.
The diagram above illustrates these relationships visually. With this foundation, the following calculations should be straightforward:
left = xtop = yright = x + widthbottom = y + heightcenterX = x + width / 2centerY = y + height / 2`
The Two Elements
There are two elements in the floating UI world: reference, which is the anchor element (button, link, etc.), and floating, which is the positioned element (tooltip, dropdown, etc.).
Once we know how to identify the positions of the two elements, we can start calculating the relationship between them.
Placement Types
Sides
There are 4 possible sides where the floating element can be positioned:
type Side = 'top' | 'right' | 'bottom' | 'left';
Alignments
There are 2 alignment options that determine how the floating element aligns along the reference edge:
type Alignment = 'start' | 'end';
Combined Placements
When you combine a side with an alignment (or leave it centered by default), you get the 12 possible placements:
type Placement =| 'top' | 'top-start' | 'top-end'| 'right' | 'right-start' | 'right-end'| 'bottom' | 'bottom-start' | 'bottom-end'| 'left' | 'left-start' | 'left-end';
Axes Explained
The Side Axis (The "Attachment" Axis)
This axis is perpendicular to the edge of the reference element. It determines how far away the floating element is from the anchor.
- For top / bottom: The Side Axis is y. You change the y coordinate to move the tooltip further up or down from the button.
- For left / right: The Side Axis is x. You change the x coordinate to move the tooltip further left or right.
The Alignment Axis (The "Sliding" Axis)
This axis is parallel to the edge of the reference element. It determines where the element "slides" along that edge to satisfy start, center, or end.
- For top / bottom: The Alignment Axis is x. The tooltip slides left or right to align its corner or center with the button.
- For left / right: The Alignment Axis is y. The tooltip slides up or down to align with the button's height.
function getSide(placement: Placement): Side {return placement.split('-')[0];}function getSideAxis(placement: Placement): Axis {const side = getSide(placement); // 'top' | 'right' | 'bottom' | 'left'return (side === 'top' || side === 'bottom') ? 'y' : 'x';}function getAlignmentAxis(placement: Placement): Axis {return getOppositeAxis(getSideAxis(placement));}function getOppositeAxis(axis: Axis): Axis {return axis === 'x' ? 'y' : 'x';}function getAxisLength(axis: Axis): 'width' | 'height' {return axis === 'x' ? 'width' : 'height';}
The Math - computeCoordsFromPlacement
When you say placement: 'bottom', you're telling Floating UI: "Put the floating element below the reference." But what does "below" actually mean in terms of x and y coordinates? This is what computeCoordsFromPlacement figures out. Let's break it down step by step.
For any placement, we need to answer:
- Where does the floating element's LEFT edge go? (the
xcoordinate) - Where does the floating element's TOP edge go? (the
ycoordinate)
Calculate Center Points
Before we position by side, we calculate where the floating element would be if it were perfectly centered on the reference:
// Center horizontally relative to referenceconst commonX = reference.x + reference.width / 2 - floating.width / 2;// Center vertically relative to referenceconst commonY = reference.y + reference.height / 2 - floating.height / 2;
So what does this formula actually mean?
Step 1: Start at the Left Edge
As mentioned above, every positioned element in the browser has an x property that represents its left edge. When we write ref.x, we're starting at the button's left edge. Think of this as our starting line. We know where the button begins, and that's our anchor point.
Step 2: Move to the Center Line
Now we need to find the center of the button. To do this, we add half of the button's width: + (ref.width / 2). If a button starts at 300px and is 200px wide, its center is at 400px (300 + 100). This gives us the vertical center line where both elements should align.
Step 3: Back Up by Half the Tooltip Width
Here's the crucial step that trips people up. If we placed our tooltip starting at the center line, it would be off-center—the tooltip's left edge would be at the center, pushing the whole tooltip too far right.
We need to back up by half the tooltip's width: - (float.width / 2). This "backs up" the tooltip so that its center (not its left edge) sits on the center line.
The formula essentially says: "Find the reference center, then offset backwards by half the floating element's width." This ensures both centers align perfectly.
Position by Side
Once we know the starting point for the floating element in x, we place the element above the reference and center it horizontally.
case 'top':coords = {x: commonX, // Use centered xy: reference.y - floating.height // Position above};
The y calculation explained:
We want floating's BOTTOM edge to touch reference's TOP edge. But we set the TOP edge (y coordinate), not the bottom. So: floating.y = reference.y - floating.height
┌─────────────────┐ ◄── floating.y = ref.y - float.height│ │ = 100 - 60 = 40│ FLOATING ││ (height: 60) │└─────────────────┘ ◄── floating bottom = 40 + 60 = 100┌─────────────────┐ ◄── reference.y = 100│ REFERENCE │└─────────────────┘
The floating element's bottom (100) meets reference's top (100)
Calculate Alignment Offset
This step we will need to know how far to shift for start/end alignment
const alignLength = getAxisLength(alignmentAxis); // If it's top/bottom placements, it returns "width" otherwise returns "height"const commonAlign = reference[alignLength] / 2 - floating[alignLength] / 2;
What is commonAlign?
For horizontal alignment (top/bottom placements):
reference.width / 2 - floating.width / 2
- If the reference wider than floating, the common align will be positive value.
- If the floating wider than reference, the common align will be negative value.
Apply Alignment
const alignment = getAlignment(placement); // 'start' | 'end' | undefinedconst alignmentAxis = getAlignmentAxis(placement);const isVertical = sideAxis === 'y'; // true for top/bottomswitch (alignment) {case 'start':coords[alignmentAxis] -= commonAlign;break;case 'end':coords[alignmentAxis] += commonAlign;break;// default (center): no adjustment needed}
- 'start' alignment: Align floating's start edge with reference's start edge
- 'end' alignment: Align floating's end edge with reference's end edge
If the placement is top, for start alignment, subtract commonAlign from x, coords.x -= commonAlign, and for end, add commonAlign from x, coords.x -= commonAlign.
Final Formula Table
Here's the complete reference for all placement calculations:
Conclusion
Understanding Floating UI's coordinate system is fundamental to mastering tooltip and popup positioning. Let's recap the key concepts:
The Coordinate System: Browser coordinates start at the top-left corner (0, 0), with X increasing rightward and Y increasing downward.
Two Elements: Every floating interaction involves a reference element (the anchor) and a floating element (the positioned content).
Placements: There are 12 placements combining 4 sides (
top,right,bottom,left) with alignments (start,end, or centered by default).Two Axes:
- The Side Axis determines the distance between elements
- The Alignment Axis determines where the floating element slides along the reference edge
The Centering Formula:
ref.x + ref.width/2 - float.width/2ensures perfect center alignment by finding the reference center and then backing up by half the floating element's size.
With this foundation, you're now equipped to debug positioning issues, customize Floating UI's behavior, or even build your own positioning logic. The next time a tooltip appears in the wrong place, you'll know exactly which axis and calculation to investigate!
Playground
Now that you understand the math behind Floating UI's positioning system, it's time to put that knowledge into practice! Use the interactive playground below to experiment with different placements and dimensions. Try adjusting the reference and floating element sizes to see how the coordinates change in real-time. Pay attention to how the Side Axis and Align Axis indicators update as you switch between placements, this will help solidify your understanding of how the two axes work together.
