ovidiuch/dragdealer

Half-pixel Precision on 2 dppx Resolution Displays

Opened this issue · 9 comments

In the case where the handle is an image carousel (such as the example on the dragdealer web page), and you drag the carousel horizontally, it will animate (slide) to a particular position defined by the number of steps. With CSS3, this animation is achieved using something like -webkit-transform: translateX(). Is the value of translateX() always set as an integer? Can it be set with a precision of 0.5 px?

I think in some cases it should have a precision of 0.5 px, not 1 px. I'll give you an example. On a device that has a display dppx of 2, there are 2 display pixels per CSS pixel. Web browsers on such devices (e.g. Safari/WebKit) actually render elements with precision of 1 display pixel – or 0.5 CSS pixels. This happens a lot when using proportional widths (%) and scaled elements, which are extremely common with responsive web development.

In the situation above, if you have an image carousel containing image slides that have a non-integer width such as 122.5 px, you would want the drag transform to step at increments of 122.5 px. This is because WebKit actually renders the image as 122.5 px wide (245 display pixels) and you would want it to line up correctly with the left and right edges of the container view. At present, the transform is only set to the nearest integer; 122 or 123 px. The result is that the edges of the image are offset from the container view by 0.5 CSS pixels, or 1 display pixel. This shows up as a thin vertical line along one edge, and depending on the contrast of the background and the image this can be very noticeable. I've found that in the Safari Web Inspector, if you manually override the transform value with 122.5 px, it then aligns perfectly in this situation.

You may need to exclude 0.5 px precision on 1 dppx displays, but if necessary you could check for this using window.devicePixelRatio. Likewise, using arbitrary float values might lead to blurring. Besides 2 dppx displays, I don't know what happens on 1.5, 2.5, or 3 dppx displays. Perhaps 3 dppx would benefit from precision of thirds? Further investigation would be needed there.

I personally don't have the JavaScript expertise to make this change myself, but could you please consider this?

Thank you.

OK, I figured out how to implement what I described above. However, the result isn't exactly as anticipated, which seems to indicate that there's some other inaccuracy present. As the amounts are less than a pixel, this could be attributed to rounding error. Perhaps a different approach would be needed? For example, when setting the transform translateX(), perhaps using the actual position value of an element inside the handle? I know that iScroll https://github.com/cubiq/iscroll/ offers this through its snap option. I'll do some more experimentation and see if anything else works/makes sense here...

@freshgoods honestly I don't have expertise with the effects of floating values in HTML for various display dppx values, nor with the particularities of translateX/Y. I can, however, confirm that we currently round to integers.

If you discover something, let us know... Thanks!

Thanks, that was indeed the part of the code that I changed in order to get half-pixel precision. However, the further problem that I encountered was that although the translateX/Y value would be more precise, the steps towards the end of the slider could still be 0.5 px off. As far as I can tell, it seems that when calculating the far extent of the slider (by subtracting the width of the slider), this also needs to have half-pixel precision, which it currently doesn't.

The number I'm referring to is the maximum translateX value when horizontally scrolling all the way to the right. The intermediate step values for translateX appear to be a simple division of that value, so if the maximum value is 0.5 px off, some of the intermediate step values will also be a fraction of 0.5 px off, particularly towards the right end of the slider, including the final step which is 0.5 px off. Do you know how/where this maximum value is calculated? Thanks.

I think this is what you're interested in: https://github.com/skidding/dragdealer/blob/master/src/dragdealer.js#L311-L326

However, there's no rounding there, so I'm not sure what could be changed.

Right, I do recall now that availWidth was appearing as an integer. If this is always the case, then I don't know if this would offer a viable solution. As such, an alternative solution - like aligning to an element - might work instead. Have you considered this before, or would you now?

Not sure what you mean by "like aligning to an element"

As I mentioned in my second comment, iScroll does this - take a look at the snap options, which allows you to scroll to named elements within the slider:
https://github.com/cubiq/iscroll/#snap

Of course, this would only prove useful here if the positions and sizes of those elements also have half-pixel precision...

OK, I think I understood what snap does in iScroll. It doesn't seem like the simplest solution for your floating point problem, but to answer your question, there are no plans to add any similar functionality in Dragdealer.

OK, thanks. I'm not sure what else can be done, but if I find anything else viable I'll let you know.