jakubfiala / atrament

A small JS library for beautiful drawing and handwriting on the HTML Canvas.
http://fiala.space/atrament/demo
MIT License
1.56k stars 115 forks source link

[question] cursor position + canvas CSS #106

Open nichoth opened 2 weeks ago

nichoth commented 2 weeks ago

I notice that if I create a new Atrament, given just a canvas, the cursor is offset from the drawing depending on the margin, etc of the canvas on the page. How to handle this?

Thanks again for writing this library; it is very helpful.

https://github.com/user-attachments/assets/8133dda5-bd76-44b0-aeb8-df588021d759

jakubfiala commented 2 weeks ago

Hey @nichoth, thanks for getting in touch. I'd have to see your code to understand what's going on there, would you mind sharing a snippet?

Some ideas: it could have to do with DPI scaling, or perhaps the canvas width + height is different to what Atrament thinks it is?

nichoth commented 2 weeks ago

Thanks @jakubfiala . Here is a simple reproduction. It should be usable with just npm i & npm start.

Is also visible at the github pages site — https://nichoth.github.io/testingatrament/

jakubfiala commented 2 weeks ago

@nichoth thanks! I believe I know what the problem is. It is indeed to do with DPI scaling.

tl;dr: A solution would be to set the canvas size in CSS, e.g. canvas { width: 400px; height: 400px; }.

A <canvas> effectively has two sizes - an intrinsic size which determines the size of the drawing area, and an extrinsic size which determines the size at which it's displayed in the browser.

When you set the width and height attributes in HTML, and you don't specify the size of the canvas element in CSS, the attributes set both the intrinsic and extrinsic size. This would be okay in most cases, but because we now have high-DPI screens, the math gets more complicated. For every CSS pixel (which is an abstract unit), Atrament has to draw 2 or more pixels on the canvas. However, the coordinates of your cursor (mouse/touch) arrive in CSS pixels, not real pixels. This is why Atrament has to scale the canvas internally and also applies a transform.

In your example, the canvas size is not defined in CSS, so when Atrament picks it up, it actually changes both the extrinsic and intrinsic sizes, so now the transform that Atrament applied doesn't fit anymore. Setting the size in CSS will ensure the canvas is exactly 400x400 (extrinsic size), even when Atrament scales the intrinsic size because of the higher DPI.

I hope you found this helpful! I'm going to think about documenting this better in the README, let me know if anything is still unclear or you have ideas for a better implementation :)

nichoth commented 2 weeks ago

Thanks for the response @jakubfiala . The solution is not so simple though. I just pushed an update that adds css for width and height on the canvas, and still having the issue. In this example, width & height and set in 3 places — html, css, and passed in to new Atrament. I tried several other permutations of setting width & height in css vs code, and haven't found the answer yet.

https://nichoth.github.io/testingatrament/

jakubfiala commented 2 weeks ago

@nichoth hmm okay, sorry to hear that! I'll try to look into it properly in the next days

nichoth commented 2 weeks ago

It looks like the canvas needs the position: fixed; in CSS.