screenX, clientX, pageX: Same Click, Three Different Numbers
The Feature That Sent Me Down This Rabbit Hole
While working on TalkToPixels my latest SaaS, I came across a situation where I had to implement the functionality to draw on canvas based on the user's pointer position. And in order to do that I was required to work with pointer click event. Basically I somehow needed the information about the exact position of pointer with respect to the canvas element on which the drawing operations were to be performed. As this was something to do with the PointerEvents so I console logged the event and found out that there were different values giving information about coordinates of the pointer on the screen, but I was confused which was the one that would be useful in my situation.
element.addEventListener("pointerdown", (event) => {
console.log(event.screenX, event.screenY);
console.log(event.clientX, event.clientY);
console.log(event.pageX, event.pageY);
console.log(event.offsetX, event.offsetY);
});
So this was the exact trigger that forced me to dive deep into this topic and I found out that the official documentation is nice but can be difficult at times to skim through entirely as it has a lot of other additional information which eventually leads the user into another rabbit hole of unknown topics. Apart from that I tried reading blogs written by others but somehow I was not satisfied with the content or the explanation.
So I decided to first understand it myself and then after implementing the feature write a blog post about the concept so that other's can also be benefitted by this. Hope this read is worth your time, so let's get started.
The One Idea That Explains Everything: The Origin
Before starting with the detailed explanation about the properties, let's address the elephant in the room. In elementary school we all have studied about the cartesian plane also known as the X/Y plane, in which there are multiple coordinates points represented as (x, y) and using one coordinate as reference point we can calculate the distance of others.
That one coordinate we choose as reference is known as the origin and trust me everything revolves around it.
To set the context, think of your screen as a 2 dimensional plane (X/Y axis). When I say the word origin it always mean (0, 0) coordinate point. When this origin is placed at different positions, the values of properties change accordingly.
All these properties (screenX/Y, clientX/Y, pageX/Y, offsetX/Y) tell us the position of the pointer from different origins.
Just keep this line in your mind The origin is where it all starts from. That said let's get started with the explanation of each of them now.
screenX/screenY vs clientX/clientY: Screen vs. Viewport
It all starts from the screen. Screen is nothing but the actual physical hardware which is made of millions of pixels, on which you are able to view and read this and almost everything. There can be multiple screens connected in a multi screen setup, but for the sake of simplicity we will assume that you are working with a single screen only.
Now in order to understand what screenX and screenY is let's place our origin to the top left of the screen.
As you can see in the diagram, the top left of the screen is marked with (0,0) and that's our origin and assume that the pointer is at the coordinate (29,17).
This simply means that our pointer is
- 29 pixels to the right from origin (x)
- 17 pixels to bottom from origin (y).
Now that we have a good understanding of screen, let's open a browser inside that and work with actual webpages. Because that’s where all the magic in web happens.
A mental model to fit in your brain before we move ahead. We can divide our browser into mainly two portions
- Top of the browser (Tabs, Address Bar, Bookmark Bar, etc.)
- Viewport (The visible area where the webpage is loaded)
In order to get the values of clientX and clientY the origin is considered from the top left of the viewport.
If you carefully observe the diagram above, you can very well see that the viewport in itself is starting at a coordinate that is not (0,0) but when calculating the value of clientX and clientY it is assumed that the top left of the viewport i.e., origin is at (0,0). The pointer sits at (29, 14) relative to the viewport, since the viewport itself starts 3 pixels below the screen's top edge.
pageX/pageY: Accounting for Scroll
Now that we have a webpage loaded inside the browser on our screen, we very well know that webpages are usually large. Only the content in the viewport region is visible and rest of the content is hidden until we scroll.
As we scroll, new content enters the viewport region and the previous one leaves the viewport region.
pageX and pageY tells us the position of the pointer from the top left of the entire document unlike clientX/clientY which is always calculated with respect to viewport's origin.
In the diagram below the pointer is at coordinate (29,17) with respect to the origin of webpage.
In order to manually calculate and verify the values of pageX and pageY, we need to perform simple mathematics taking into consideration the value of scrollX/Y and the clientX/Y.
pageX = clientX + window.scrollX = 29 + 0 = 29
pageY = clientY + window.scrollY = 12 + 5 = 17
scrollX and scrollY are global properties given by the browser's window object. They simply tell, how much of the document is scrolled from the top left of the webpage origin in both X and Y axis respectively.
offsetX/offsetY: Position Relative to an Element
This is the exact property that I wanted to use in the situation I mentioned in the start.
Whatever properties we studied so far had a fixed origin, using which the position of pointer was calculated. But often times we have a requirement to get the position of pointer with respect to a certain element, that means we need to shift the origin (0,0) to the top left portion of that element itself.
offsetX and offsetY give us the position of pointer with respect to the element on which the event has been fired. For instance in the diagram below the pointer is at coordinate (10,4) with respect to the origin of element.
NOTE : It can also be calculated mathematically using the above studied properties and the getBoundingClientRect() method called on the element.
We will not be going into detail about getBoundingClientRect() method in this blog post as it is out of scope but in a nutshell, this function returns us with certain information about an element like the height, width, its placement with respect to the origins that we just studied above.
Cheat Sheet: Which Property Do You Actually Need?
Below is a reference table for you that you can refer next time whenever working with pointer events.
Closing Thoughts
I hope that by now you might have a developed a decent understanding about this topic.
In the era we are living today, writing code has become a piece of cake. Anyone can use AI and write code but the difference that sets you apart from the crowd is having in depth understanding about the concepts. Not only it can save our time in debugging several issues that are bound to happen one day or another but also steer the AI's output in better direction.
So, at the end I would like to thank you for taking out your valuable time for reading this and hope you always stay happy, healthy and blessed.
This is Mukul Padwal signing off.
If you have any doubt, suggestions about what could be improved you can always reach me out via my socials. Links below