For a web application, I need to display small images as a circle and draw a circular gradient-filled border around them using only HTML and CSS. For unknown reasons, some systems reproducibly show misalignment between the image and the border so that they are not concentric. On affected systems, this behavior is visible in both Chrome, Firefox, and Edge, however, the direction of the misalignment is different. Other systems, however, are perfectly fine.
Enlarged screenshot of the misalignment in my web application:
My first thought was, that this might be a subpixel rendering issue but since I am using an even-numbered image size of 24x24px and an even-numbered border width of 2px this seems unlikely. I did some experiments by gradually increasing the image size by 1px and found that the direction and extent of misalignment are inconsistent and sometimes there seems to be an oval distortion. Below you find a reduced code snippet at screenshots from Chrome, Firefox, and Edge. I indicated the direction of misalignment in red. Increasing the border width yielded similar results, but the effect seems most pronounced with 2px.
.rounded-corners-gradient-borders {
box-sizing: border-box;
padding: 2px;
border-radius: 100%;
background: linear-gradient(45deg, #F48ACE 0%, #493A97 100%);
}
<img class="rounded-corners-gradient-borders" src="https://i.picsum.photos/id/368/24/24.jpg?hmac=qTESgqsVn81m_y-i5SDjG0xncWcyf-gYC40Jw9amc-k" />
https://codepen.io/grilly17/pen/VwXNrMO
Annotated screenshot of Codepen output in Firefox:
Annotated screenshot of Codepen output in Chrome:
I am aware that drawing a perfectly concentric "solid colored" border can be achieved a lot easier, but the gradient is a hard requirement in this case.
Since it doesn't seem to affect all systems, I asked friends and colleagues to have a look at different OS types, OS versions, browsers, browser versions, monitors, screen resolutions, and different compute hardware but I was not able to find a common cause for this. The direction and extent of misalignment seemed to be different on every system and browser but it does not change when reloading the page in the same browser again. So it appears to be deterministic.
At this point, my best guess is that it is related to some rounding error in the rendering process, but I would love to get to the bottom of this. Does anybody know why this is happening at all and why it is only affecting some systems? Is there a better solution to achieve this?
Thanks to the hint of "CSS pixels vs screen pixels" I was able to understand the root cause and find a solution to my problem. I should have realized that the screenshot of the icon was 35px high instead of the expected 28px including padding.
Most OS have a display setting for "scaling" up everything on your screen by a certain factor, e.g. 125%. This affects everything on your screen and may cause fractional pixel values, which results in the effect described above. If you have multiple screens, the value might be different on every screen. For web applications, the active screen's scaling value is applied only on page loading/rendering and not when moving the page between screens.
The scaling factor can be accessed via the JavaScript window property window.devicePixelRatio.
Using this I was able to work out two acceptable solutions, which might be useful for others:
Get a "device pixel perfect" representation by undoing the scaling
Get a "no subpixel" representation by accounting for pixel fractions in the unscaled value
The enlarged screenshot below shows from left to right the original misaligned image, the "device pixel perfect" image, and the "no subpixel" image when using a display scaling of 125%.
Here is my code (tested on FF, Chrome, Edge): https://codepen.io/grilly17/pen/QWmegPj
function precompensateScaling(value, scale) {
return value / scale;
}
function precompensatePixelFractions(value, scale) {
return value - value * scale % 1 / scale;
}
// wait until page is fully loaded
window.onload = (event) => {
const original = document.getElementById('original');
const oHeight = parseFloat(window.getComputedStyle(original).getPropertyValue('height'));
const oPadding = parseFloat(window.getComputedStyle(original).getPropertyValue('padding'));
const scale = window.devicePixelRatio;
const unscaled = document.getElementById('unscaled');
//unscaled.style.transform = `scale(${1/scale})`; // alternative
unscaled.style.height = `${precompensateScaling(oHeight, scale)}px`;
unscaled.style.padding = `${precompensateScaling(oPadding, scale)}px`;
const adjusted = document.getElementById('adjusted');
adjusted.style.height = `${precompensatePixelFractions(oHeight, scale)}px`;
adjusted.style.padding = `${precompensatePixelFractions(oPadding, scale)}px`;
};
Thank you all for your support. I <3 the Stack Overflow community!
Related
I really hope, you are able to assist me on this one, as I'm tearing my hair out...
I have a little marquee, based on this code: http://jsfiddle.net/TCJT4/525 that feeds some text.
Here's how it looks on an iPad 6... and please disregard from the preliminary design, but this is how it should look:
Here's how it looks on an iPhone 4S:
The ticker is retrieved from the exact same source, but as you can see, the text appears larger on the iPhone (the iPad image is zoomed, so it appears larger, but in reality, they are both displaying a 320x30 pixels placeholder. The text is temporarily hardcoded to 20px in height and I've tried using other units as well... the banner still looks different on the devices.
I did some debugging of the ticker container/placeholder, as well as the detected banner height and disabled all text-adjusting elements. Here's a result of some of the properties:
iPad 6: Tickerplaceholder DIV-height: 24pixels, bannerheight: 30px, pixelaspect-ratio: 2
iPhone: Tickerplaceholder DIV-height: 32pixels, bannerheight: 30px, pixelaspect-ratio: 2
PC (Chrome): Tickerplaceholder DIV-height: 24pixels, bannerheight: 30px, pixelaspect-ratio: 1;
I find it very strange that two retinadisplay devices display the same banner differently - and that the iPad and the PC displays them correctly.
The ticker can also be found here in its latest form: www.videobanner.dk/ph.html
Pixels are different physical sizes on different devices - so 24px is smaller on one device than on another.
For text, if you use points instead then the size will be the same across devices - they will all make 72pt 1 inch (thereabouts).
Of course this means you have to use text and not bitmaps etc.
Mobile devices may also have a zoom level set for readability (by the user) which will also affect the size - eg you specify 24pt or px and the browser makes it 36pt or px - the calculated size in the inspector will be different to the styled size - to get around this you need to set a value somewhere, then see what it actually is when rendered and apply a ratio to get what you want (via javascript). I've used code like this in the past to ensure text fitted in a box of a given pixel size;
var fontScale = 1 ;
var mySpecifiedFontSize = 24 ;
var myTextElement = document.getElementById("MY_TEXT_ELEMENT_ID") ;
function fontScalingCorrection(){
var style = window.getComputedStyle(myTextElement);
var fontSize = parseInt(style["font-size"]);
if(!isNaN(fontSize)){
if(fontSize !== mySpecifiedFontSize){
fontScale = (mySpecifiedFontSize / fontSize) * fontScale ; //allows for multiple calls
myTextElement.style.fontSize = (fontScale * mySpecifiedFontSize) + "px" ; //or units used
}
}
}
//after the element has been drawn once ( or use another element as a size marker )
fontScalingCorrection();
The cause of the problem is related to a quirk or error in iOS Safari, which returned an incorrect and unpredictable height when dealing with unordered lists, containing text of various lengths. This became apparent when I compared different text lenghts on different platforms. No fix has been found, but I was able to circumvent the problem by splitting one string into several shorter strings, such as
<li>This is a text that </li><li>doesn't go well with iOS</li>
In my project, this solution also works... perhaps not that pretty, though.
Problem: Certain values of matrix3d cause a div not to render at all, but the inspector's highlight area for the div is the correct shape.
I have an app which uses the camera to recognize four dots near the corners of a rectangular object. I calculate a matrix from the positions of those dots to map a div with a certain size to the quadrilateral defined by the screen positions of the dots. This is all fine, but at certain arrangements of the dots, the div being transformed by the matrix does not render at all. After implementing this, I found this example, which does much the same thing I'm doing, albeit without the AR: http://franklinta.com/2014/09/08/computing-css-matrix3d-transforms/
If you inspect a div with such a matrix, the blue highlight has the correct shape, but the div doesn't render. It also seems not to calculate pointer events, otherwise the cursor style would be visible. If you run the following snippet, you should be able to inspect the div and highlight it to see the shape is ought to have.
.rect {
position:absolute;
left:0;
top:0;
width:736px;
height:414px;
transform-origin:0 0;
background:red;
cursor:crosshair;
}
<div class="rect" style="transform:matrix3d(0.009359283667, 0.010930981398, 0, 0.000104500516, -0.092142538522, -0.121434733223, 0, -0.000225136056, 0, 0, 1, 0, -21.813928384019, -10.640940675131, 0, -0.106409406751)"></div>
<div>Inspect my invisible sibling...</div>
I'm currently using .toFixed(12) on each element of the matrix after calculation and right before applying it to the div. In my prior research to solve this, it was suggested that it could be a precision problem, but that doesn't seem to be the case. I have tried down to .toFixed(6), but that served only to make the matrix less correct.
I see this in Chrome, Version 42.0.2311.135 (64-bit) and Safari Version 7.1.5 (9537.85.14.17). I haven't tried other browsers.
Now, Frank Ta's example doesn't seem to have the same problem as mine, so I suspect there is some way I could calculate the matrices differently such that they would render correctly. However, that's not relevant to what I'm asking. My question is this: Why do these certain matrices cause normal rendering to fail, but highlighting them in the inspector shows the expected shape?
I can give a list of additional examples of such matrices if it would help.
I have the following goal: I wanted to place a heart within a container - scaled and positioned.
First I wanted to use an icon font but I've discarded the idea. Second option to load the heart as an image I've discarded too - I have to use the heart a few times on my recent project and I wanted to save http requests. Therefore I wanted to go with the SVG as a background-image option. But the problem is, somehow I am unable to tame that beast. I've built a sample pen to illustrate the issues and parts I don't understand.
The un-base64-encoded optimized SVG looks like that:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 960"><polygon points="756,168.4 593.5,168.4 480,258.3 366.5,168.4 204,168.4 30,349 480,791.6 930,349"/></svg>
The sample code you can find from my codepen.
Basically I have three related questions (normally I prefer to post separate issues but those three questions are basically way too connected therefore I hope it's ok):
The sizing: .heart1 has a width and height of 100% and everything displays fine. If you use suiting px values all is fine too but if you try to enter ems the heart isn't shown anymore. Why?
The green box: .heart1 has a width of 100% but if you drag the browser window bigger the heart only grows to some point and then only the green box keeps on growing. I thought SVGs are more or less able to scale to "infinity"?
The yellow box: My basic goal was to make the heart a bit smaller than the width of the yellow box, center it horizontally within and give the heart some top margin. Width and height of .heart2 are set to 75%. But somehow I am unable to position the heart within the box neither with top, left and/or right properties nor in background:url with "no-repeat center 2em" e.g. . It just doesn't react.
I use a block of code shown below to fit svg in a DIV. It works best in a DIV with the same width/height. As you can see below it uses getBBox() to change its viewBox, plus changes the svg width/height values.
It works cross browser: IE10+/CH31/FF23
var bb=mySVG.getBBox()
var bbw=bb.width
var bbh=bb.height
//--use greater of bbw vs bbh--
if(bbw>=bbh)
var factor=bbw/divWH
else
var factor=bbh/divWH
var vbWH=divWH*factor
var vbX=(bbw-vbWH)/2
var vbY=(bbh-vbWH)/2
//---IE/CH---
if(!isFF)
{
var ViewBox=mySVG.viewBox.baseVal
ViewBox.x=vbX
ViewBox.y=vbY
ViewBox.width=vbWH
ViewBox.height=vbWH
}
else
mySVG.setAttribute("viewBox",vbX+" "+vbY+" "+vbW+" "+vbH)
//--requred for FF/CH---
if(!isIE)
{
mySVG.setAttribute("width","100%")
mySVG.setAttribute("height","100%")
}
else
{
mySVG.removeAttribute("width")
mySVG.removeAttribute("height")
}
The svg is centered both left/right and top/bottom within the DIV, plus maintains its aspect ratio. This should help get you started.
Let's say i have a div that i've defined to be (32px, 32px) in size:
html:
<div id="theBox"></div>
css:
div {
width: 32px;
height: 32px;
background-color: gray;
}
(Live jsFiddle view)
How can i get the actual size of the box in pixels?
You'll note that the box doesn't have to be 32px. It can be larger:
or smaller:
or exactly 32 pixels:
The reason for the differences, of course, is because Chrome and Internet Explorer allow me to zoom.
i would like to know the actual size of the element. Why? No reason; just cause. i'm curious, and i'd like to broaden the limits of human knowledge and understanding.
Or because i need to set the internal resolution of a Canvas element to match the actual size of the canvas element - otherwise the rendered canvas contents will get stretched without my permission:
Although, my reasons for wanting to know the size of an element do not necessarily apply just to a Canvas. i'm asking about a generic div element; and the answer will be used towards canvas, img, video, and anything else i desire.
You would need to detect the zoom level.
Then write a simple arithmetic proportion to calculate the 'actual' size, or the size as it appears to the user.
var zoomLevel,
, actualSize = 32
, viewSize;
function getZoomLevel(){ ... your code here...return zoomLevel;}
function getViewSize(actualSize){
viewSize = actualSize*getZoomLevel();
return viewSize;
}
Then ... call getViewSize() when ready ...
Hopefully the math is clear enuff.
Solving for y (or viewSize):
actualSize/1 = y/zoomLevel
However, you will need to be careful about sub-pixel precision, especially among the notoriously bad length/width determining browsers like IE9. But, as long as all you need is something close, this should work.
I'm a bit confused with the way the canvas element anti-aliases text and am hoping you all can help.
In the following screenshot the top "Quick Brown Fox" is an H1 element and the bottom one is a canvas element with text rendered on it. On the bottom you can see both "F"s placed side by side and zoomed in. Notice how the H1 element blends better with the background:
Here's the code I'm using to render the canvas text:
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'black';
ctx.font = '26px Arial';
ctx.fillText('Quick Brown Fox', 0, 26);
}
Is it possible to render the text on the canvas in a way so that it looks identical to the H1 element? And why are they different?
It's now possible to get sub-pixel font rendering by creating an opaque canvas context. In Safari and Chrome you can get this using this snippet:
var ctx = canvas.getContext("2d", {alpha: false})
I found this from this blog post.
Answering my own question:
It is possible using the technique demonstrated on this site:
https://bel.fi/alankila/lcd/
The only problem is that its too slow to implement in a production app. If anyone runs across a faster way please let me know.
Matt, I sat with the (same/similar) problem last week, which, in my case, turned out to be because of differences in pixel densities on the devices I was testing; I wrote about it tonight - http://joubert.posterous.com/crisp-html-5-canvas-text-on-mobile-phones-and
The link at posterous is dead, so here is the gist with the source code:
https://gist.github.com/joubertnel/870190
And the snippet itself:
// Output to Canvas without consideration of device pixel ratio
var naiveContext = $('#naive')[0].getContext('2d');
naiveContext.font = '16px Palatino';
naiveContext.fillText('Rothko is classified as an abstract expressionist.', 10, 20);
// Output to Canvas, taking into account devices such as iPhone 4 with Retina Display
var hidefCanvas = $('#hidef')[0];
var hidefContext = hidefCanvas.getContext('2d');
if (window.devicePixelRatio) {
var hidefCanvasWidth = $(hidefCanvas).attr('width');
var hidefCanvasHeight = $(hidefCanvas).attr('height');
var hidefCanvasCssWidth = hidefCanvasWidth;
var hidefCanvasCssHeight = hidefCanvasHeight;
$(hidefCanvas).attr('width', hidefCanvasWidth * window.devicePixelRatio);
$(hidefCanvas).attr('height', hidefCanvasHeight * window.devicePixelRatio);
$(hidefCanvas).css('width', hidefCanvasCssWidth);
$(hidefCanvas).css('height', hidefCanvasCssHeight);
hidefContext.scale(window.devicePixelRatio, window.devicePixelRatio);
}
hidefContext.font = "16px Palantino";
hidefContext.fillText("Rothko is classified as an abstract expressionist.", 10, 20);
Here's a way of doing sub-pixel rendering for any canvas content (text, images, vectors, etc). http://johnvalentine.co.uk/archive.php?art=tft.
Outline of the method
It draws onto a canvas, which is then drawn to the screen to take advantage of RGB-striped subpixels. It works with alpha channels too. Note that this might not work if you are using a portrait display, non-striped pixels, or if your browser displays canvases at a lower resolution than your display.
There's scope for fine-tuning, but it's a big gain for a simple method.
This is generically called subpixel anti-aliasing, or ClearType on Windows. I'm not aware of any OS/browser combinations that currently support this for Canvas.
I'd be interested to see some tests using sub-pixel offsets for the text to see if any browsers even use pixel-based hinting of the font rendering (aligning ascenders on pixel boundaries, for example). My assumption would be no.
Edit: My assumption was wrong; it would appear that Safari, Chrome, and Firefox all utilize some pixel font hinting. Safari and Chrome appear the same, snapping to whole pixel boundaries, but are different from Firefox (snapping to half-pixel boundaries?). See the visual results of testing (on OS X) here: http://phrogz.net/tmp/canvas_text_subpixel.html
You could make the fonts a lot clearer with fairly easy technique.
You can scale the canvas in CSS twice as small:
canvas {
transform-origin: left top;
transform: scale(0.5);
}
In the HTML double the dimensions of the canvas:
<canvas width="(width*2)" height="(height*2)">
Finally draw everything on the canvas in double size.
You will notice that the fonts are a lot clearer.
This is not completely the same as H1 in the HTML but a looks lot better than normal font rendering.