Aspect ratio behaviour of <canvas> - internal dimensions and element size - css

As you know, the <canvas> element in HTML5 has an internal size, set via elm.width and elm.height where elm is the DOM element.
The element within the DOM itself can have a different box size, set via CSS.
When the two differ, the browser automatically stretches the contents of the <canvas> to fit the content box size. Can this behaviour be adjusted without an extra element? Can it be set to something like background-size: contain so that it keeps the aspect ratio of the inner canvas size?
As far as I know, there is no standard way to do this, so webkit-specific hacks are also accepted. But if there is no way to do it at all, what's the most elegant to do it with a wrapper element (we all know that we can wrap the element and adjust it via JS on resize)?
The one solution I can think of is actually using background-size: contain and background: element() but this is Firefox-specific, and I am looking for a WebKit solution.
Side question, can a difference in element box and canvas internal sizes impact performance? If so, how much?

Yes, there is a new CSS property called object-fit. This allow you to set the content to for example cover which will then force aspect ratio to be kept at the same time as filling the DOM box.
Simply add this rule to the canvas element:
canvas {
object-fit: cover;
}
Support
(Updated) It is (partially) supported in Safari, but not in IE. Opera Mini needs a -o- prefix.
Does not seem to work with WebGL canvas (or video) in Chrome likely related to these issues: issue #1, issue #2, issue #3.
As a fallback the size can be calculated manually. This can be used with drawImage. If drawImage is not an option it will require a wrapper element with overflow set to hidden to work.
Example
Both canvas' below are using the default bitmap size of 300x150. They are then stretched by defining CSS size 300x300 pixels.
Normally this would make the circle an oval. However, the canvas on the right will use the new object-fit rule set to cover, the bitmap will be scaled considering aspect ratio so we still get a circle, but of course we'll also loose some of the edges (as well as sharpness).
var ctx1 = document.getElementById("c1").getContext("2d");
var ctx2 = document.getElementById("c2").getContext("2d");
drawCircle(ctx1, 150, 75, 70); // draw circle to stretched canvas 1
drawCircle(ctx2, 150, 75, 70); // draw circle to object-fit/cover canvas 2
function drawCircle(ctx, x, y, r) {
ctx.moveTo(x + r, y);
ctx.arc(x, y, 70, 0 , 6.28);
ctx.closePath();
ctx.stroke();
}
#c1, #c2 {
width:300px;
height:300px;
}
#c2 {
-o-object-fit: cover; /* for Opera Mini */
object-fit: cover; /* W3C version (incl. FF/webkit) */
}
<canvas id="c1"></canvas><canvas id="c2"></canvas>

Related

How to prevent fractional pixels in an element with width set to auto?

I have a row of inline-block elements that all have auto width, so they draw as wide as the varying text content in each of them plus a bit of padding. This results in the actual width of each element having fractional pixels.
This would be fine, but each element contains an icon font that is very sensitive to being drawn when not aligned to the pixel grid, the subpixel rendering of it just looks nasty and blurry if the glyph's origin is not at an integer pixel value.
How can I keep the width of these elements dynamic while preventing decimal pixel values? For example, one of the elements ends up with the width 60.183px when I'd like it to be rounded up to 61px. Sass can do ciel(), which would be perfect, but there is seemingly no way to apply it to auto values.
Or alternatively, is there any way I can ensure that the icon glyphs have an origin that is a full integer pixel without rounding the container widths?
Because of the way these elements are used I'd like to avoid doing this with JS and find a CSS/SASS only solution.
I have found that if you use display: inline-table; instead of display: inline-block; it will force it to render exact pixel widths/heights. This may fix your issue however using table/inline-table will make the space within container collapse in some browsers so you would have to wrap the contents in another element.
The solution may someday be the CSS property round() which can round dimensions to whole pixels.
width: round(var(--width), 50px);
width: round(up, 101, 10);
width: round(down, 106, 10);
margin: round(to-zero, -105, 10);
This may be the solution at some point in time, but as of early 2023 it is only supported in Safari.
If this property is ever widely adopted make sure to provide a fallback value like this:
.my-box
{
width: 25vw;
width: round(up, 25vw);
}

flot.js - position ticks vertically, but being cut off and centered

I am using flot.js for charts with timestamp on the x-axis. As I will have quite a lot of ticks on these charts I am rotating them vertically so they do not overlap. This works fine, but the labels are centered on the tick and there is not enough room provided so they are cutoff.
I am NOT using the tickrotor plugin at tickrotor. I tried it and there were more problems than benefits. Instead I am using plain css which I found here on SO via rotate tick labels, however, in the post it appears they are not having the problem I am (perhaps there was some missing information there?).
Furthermore, I need to support IE8. The post mentioned above talks about using filter or -ms-filter, but fails to mention the css to accomplish that.
I was under the impression that even with the tick rotation it would correct the height accordingly, but that doesn't seem to the case. I also checked out flot's github and while they mention working on this feature it has still not been implemented.
I know a lot of people have the need to rotate the ticks, but I have not found anything which resembles my issue (centered and cutoff).
Any help or ideas would be appreciated.
#flot_chart div.xAxis div.tickLabel
{
transform: rotate(-90deg);
-ms-transform:rotate(-90deg); /* IE 9 */
-moz-transform:rotate(-90deg); /* Firefox */
-webkit-transform:rotate(-90deg); /* Safari and Chrome */
-o-transform:rotate(-90deg); /* Opera */
/*rotation-point:50% 50%;*/ /* CSS3 */
/*rotation:270deg;*/ /* CSS3 */
}
You are going to have lots of problems rotating the labels yourself. This is why the flot-tickrotor exists after all.
Flot sizes it's canvas within the placeholder div to leave enough room for the labels. By rotating them through CSS you are 1.) rotating around the center of the div (hence they go into the canvas area - hint you'll need to shift them down) and 2.) you are overflowing the div container (hint - you'll need to exapand the parent placeholder div). The example you link to works because the text is wrapped and about the same size rotated or not rotated.
Now, the flot-tickrotor deals with these by hooking the low level code of flot and resizing the canvas to make room for the rotated labels (it also gets rid of the label divs and draws the labels using the canvas - which helps with old IE).
So if you really want to pursue rotating the labels yourself, study the code to the plugin and have fun recreating it's functionality.
EDITS
Here's an attempt to use your CSS and make some on the fly adjustments so things will fit:
// push the labels down half their width
// while we are at it find longest label height
var longest = -1;
$('#flot_chart .xAxis .tickLabel').each(function(){
var self = $(this);
var width = self.width();
self.css('top',parseInt(self.css('top')) + width/2 - 5);
if (width > longest){
longest = width;
}
});
// now adjust the chart height so we don't overflow
$("#flot_chart").height($("#flot_chart").height() + longest - $('#flot_chart .xAxis .tickLabel').height() + 5);
See fiddle demonstration here.
Give Padding Bottom and position relative to your main container.
And, give position absolute to your labels and give bottom 0px.
like,
.main-container { padding-bottom:50px; position:relative; }
and
#flot_chart div.xAxis div.tickLabel
{
position:absolute;
bottom:0px;
transform: rotate(-90deg);
-ms-transform:rotate(-90deg); /* IE 9 */
-moz-transform:rotate(-90deg); /* Firefox */
-webkit-transform:rotate(-90deg); /* Safari and Chrome */
-o-transform:rotate(-90deg); /* Opera */
/*rotation-point:50% 50%;*/ /* CSS3 */
/*rotation:270deg;*/ /* CSS3 */
}
I hope this will solve your issue.
If not, than please show your full code via Jsfiddle.
This is a very old question still posting my experience.
I too faced the same issue so I started a trial and error and found that if the max-width is set to a lower value it works fine.
Below is the css that fixed the issue for me (in addition to the the one mentioned in the question).
300 is the max height of the graph I am using,
the max width being set earlier was 71px
#flot-placeholder div.xAxis div.tickLabel {
max-width : 30px !important;
top: 300px !important;
}

Increment pixel by pixel over a gradient background image

I got an image that contains a complex gradient with different intermediate colors. height: 1px; width:100px
I got div blocks with a background color.
I want the background color of the div blocks to come from the gradient image.
For example : .div50 class will have the 50th pixel of the gradient image as background and repeat-x and repeat-y.
What is the best way to achieve this ?
Thanks !
There doesn't currently seem to be a cross-browser solution to this.
w3 seems to be developing a cool solution to this problem of being unable to use "image fragments".
They propose cropping the image within the actual image call.
Instead of using:
background-image: url('swirl.png'); /* old UAs */
They propose:
background-image: image('sprites.png#xywh=10,30,60,20'); /* new UAs */
Due to the fact that this is still in the works, it probably isn't so useful to you...
If you need a solution now, use Mozilla:
background: -moz-image-rect(url('Image.jpg'), 0, 1, 1, 0);
This will get the first pixel (top left) of your image.
-moz-image-rect takes five values:
Image URL
Top-The distance to begin from the top.
Right-The distance to end from the left.
Designates the width.
Must always be larger than "Left".
Bottom-The distance to end from the top.
Designates the height.
Must always be larger than "Top".
Left-The distance to begin from the left.
Example Only viewable in Mozilla browsers
Made using this image:
-moz-image-rect(url('Image.jpg'), 10, 100, 25, 50);
Image: Image.jpg
This image will be cropped to 15px tall (starting immediately after the 10th pixel and continuing to the 25th pixel) and 50px wide (starting immediately after the 50th pixel and continuing to the 100th pixel).
A somewhat simple no-image-required solution, would be to use a pixel color finder, such as instant eyedropper, to figure out the 100 different colors you would need to represent all of the pixels in your 1x100 image. Using those colors, you can very easily create backgrounds that will function cross-browser.
Hope this helps!
I'd do it like this:
HTML :
<div class="background_color">
<div class="gradient_50"></div>
</div>
CSS :
.background_color{
background-color:#121212;
position:relative;
height:150px;
width:300px;
}
.gradient_50{
background-color:red;
height:50px;
position:absolute;
bottom:0px;
width:100%;
}
JsFiddle you can put background-image for your effect

force css to display image with ratio-aspect

imax is a div element.
#imax {
width:222px;
height:222px;
}
#imax img {
width:222px;
height:auto;
}
if either way I tried auto on width or height, the images with different orientation will result in distort in either scale. How could I fix to display with ratio-aspect?
Leave out the height:
#imax img {
width:222px;
}
And the browser will take care of the aspect ratio calculation.
The image is distorted since you put restrictions on it's parent. If you specify just one of the size attributes, the other should scale according to proportions - unless it's limited/affected by its container. Might work differently if you used max-height/width on the container
Cannot be done with CSS only.
You could use Javascript to do this: Get either height or width of the full size image, then calculate the correct smaller size.

Best technique to center an element using position relative/absolute?

I'm trying to get consistent centering cross browser when I set the position property of the parent element to relative and the position of the child to absolute (the child is the element I'm trying to center)
Currently in firefox it's not exactly center, but ie6/7 it is (sample of what i'm using below)
#wrapper { min-width:995px; position: relative; }
#wrapper2 { margin:0 auto; z-index: 0; position: absolute; }
Are you trying to horizontall center, vertically center or both?
In either case use of margin and absolute is incompatible. Position absolute takes it out of the normal flow. If you take out the position absolute the inner div is horizontally centered correctly.
Whatever you want to do, centering (especially vertical centering) is a huge problem in pure CSS, particularly if IE6 support is required.
If you must use absolute positioning on the inner div, you pretty much need to rely on pixel positioning, which means knowing the fixed sizes of the outer and inner divs (barring a few corner cases).
If you're interested in a jQuery approach, I used the following a few weeks ago:
$(window).load(function() {
$("ul.recent-list div img").each(function() {
var moveX = ($(this).width() / 2 * -1) + 18;
var moveY = ($(this).height() / 2) * -1 - 18; // 18 is 1/2 the default offset of 36px defined in CSS
$(this).css({'top' : moveY, 'left' : moveX});
});
});
I had to take an image of an unknown size and position it within a "window" of 36x36px so that only the 36 square px area of the image was visible through the window <div>.
The (window).load was needed instead of (document).ready because Safari cannot use the width() and height() functions if no width/height attributes are defined on the image on document.ready. Since my images were of varying dimensions, I had to use window.load instead.

Resources