I have 2 canvases, one uses HTML attributes width and height to size it, the other uses CSS:
<canvas id="compteur1" width="300" height="300" onmousedown="compteurClick(this.id);"></canvas>
<canvas id="compteur2" style="width: 300px; height: 300px;" onmousedown="compteurClick(this.id);"></canvas>
Compteur1 displays like it should, but not compteur2. The content is drawn using JavaScript on a 300x300 canvas.
Why is there a display difference?
It seems that the width and height attributes determine the width or height of the canvas’s coordinate system, whereas the CSS properties just determine the size of the box in which it will be shown.
This is explained in the HTML specification:
The canvas element has two attributes to control the size of the element’s bitmap: width and height. These attributes, when specified, must have values that are valid non-negative integers. The rules for parsing non-negative integers must be used to obtain their numeric values. If an attribute is missing, or if parsing its value returns an error, then the default value must be used instead. The width attribute defaults to 300, and the height attribute defaults to 150.
To set the width and height on a canvas, you may use:
canvasObject.setAttribute('width', '150');
canvasObject.setAttribute('height', '300');
For <canvas> elements, the CSS rules for width and height set the actual size of the canvas element that will be drawn to the page. On the other hand, the HTML attributes of width and height set the size of the coordinate system or 'grid' that the canvas API will use.
For example, consider this (jsfiddle):
var ctx = document.getElementById('canvas1').getContext('2d');
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 30, 30);
var ctx2 = document.getElementById('canvas2').getContext('2d');
ctx2.fillStyle = "red";
ctx2.fillRect(10, 10, 30, 30);
canvas {
border: 1px solid black;
}
<canvas id="canvas1" style="width: 50px; height: 100px;" height="50" width="100"></canvas>
<canvas id="canvas2" style="width: 100px; height: 100px;" height="50" width="100"></canvas>
Both have had the same thing drawn on them relative to the internal coordinates of the canvas element. But in the second canvas, the red rectangle will be twice as wide because the canvas as a whole is being stretched across a bigger area by the CSS rules.
Note: If the CSS rules for width and/or height aren't specified then the browser will use the HTML attributes to size the element such that 1 unit of these values equals 1px on the page. If these attributes aren't specified then they will default to a width of 300 and a height of 150.
The canvas will be stretched if you set the width and height in your CSS. If you want to dynamically manipulate the dimension of the canvas you have to use JavaScript like so:
canvas = document.getElementById('canv');
canvas.setAttribute('width', '438');
canvas.setAttribute('height', '462');
The browser uses the css width and height, but the canvas element scales based on the canvas width and height. In javascript, read the css width and height and set the canvas width and height to that.
var myCanvas = $('#TheMainCanvas');
myCanvas[0].width = myCanvas.width();
myCanvas[0].height = myCanvas.height();
Shannimal correction
var el = $('#mycanvas');
el.attr('width', parseInt(el.css('width')))
el.attr('height', parseInt(el.css('height')))
Canvas renders image by buffer, so when you specify the width and height HTML attributes the buffer size and length changes, but when you use CSS, the buffer's size is unchanged. Making the image stretched.
Using HTML sizing.
Size of canvas is changed -> buffer size is changed -> rendered
Using CSS sizing
Size of canvas is changed -> rendered
Since the buffer length is kept unchanged, when the context renders the image,
the image is displayed in resized canvas (but rendered in unchanged buffer).
CSS sets the width and height of the canvas element so it affects the coordinate space leaving everything drawn skewed
Here's my way on how to set the width and height with Vanilla JavaScript
canvas.width = numberForWidth
canvas.height = numberForHeight
I believe CSS has much better machinery for specifying the size of the canvas and CSS must decide styling, not JavaScript or HTML. Having said that, setting width and height in HTML is important for working around the issue with canvas.
CSS has !important rule that allows to override other styling rules for the property, including those in HTML. Usually, its usage is frowned upon but here the use is a legitimate hack.
In Rust module for WebAssembly you can do the following:
fn update_buffer(canvas: &HtmlCanvasElement) {
canvas.set_width(canvas.client_width() as u32);
canvas.set_height(canvas.client_height() as u32);
}
//..
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
// ...
let canvas: Rc<_> = document
.query_selector("canvas")
.unwrap()
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap()
.into();
update_buffer(&canvas);
// ...
// create resizing handler for window
{
let on_resize = Closure::<dyn FnMut(_)>::new(move |_event: Event| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
// ...
window.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())?;
on_resize.forget();
}
}
There we update the canvas buffer once the WASM module is loaded and then whenever the window is resized. We do it by manually specifying width and height of canvas as values of clientWidth and clientHeight. Maybe there are better ways to update the buffer but I believe this solution is better than those suggested by #SamB, #CoderNaveed, #Anthony Gedeon, #Bluerain, #Ben Jackson, #Manolo, #XaviGuardia, #Russel Harkins, and #fermar because
The element is styled by CSS, not HTML.
Unlike elem.style.width & elem.style.height trick used by #Manolo or its JQuery equivalent used by #XaviGuardia, it will work for canvas whose size is specified by usage as flex or grid item.
Unlike the solution by #Russel Harkings, this also handles resizing. Though I like his answer because it is really clean and easy.
WASM is the future! Haha :D
P.S. there's a ton of .unwrap() because Rust explicitly handles possible failures.
P.P.S.
{
let on_resize = Closure::<dyn FnMut(_)>::new(move |_event: Event| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
// ...
window.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())?;
on_resize.forget();
}
can be done much cleaner with better libraries. E.g.
add_resize_handler(&window, move |e: ResizeEvent| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
})
If you want a dynamic behaviour based on, e.g. CSS media queries, don't use canvas width and height attributes. Use CSS rules and then, before getting the canvas rendering context, assign to width and height attributes the CSS width and height styles:
var elem = document.getElementById("mycanvas");
elem.width = elem.style.width;
elem.height = elem.style.height;
var ctx1 = elem.getContext("2d");
...
I'm creating a simple card that contains a long description.
I want to set the normal height of the card to 50px and when the user clicks on it I want to expand the card to be able to fit all the content inside.
So far, to obtain my needs I wrote this:
String aLotOfText = "When Gradle resolves the compile classpath, it first resolves the runtime classpath and uses the result to determine what versions of dependencies should be added to the compile classpath. In other words, the runtime classpath determines the required version numbers for identical dependencies on downstream classpaths. Your app's runtime classpath also determines the version numbers that Gradle requires for matching dependencies in the runtime classpath for the app's test APK. The hierarchy of classpaths is described in figure 1.";
bool cardExpanded = false;
InkWell(
onTap: () {
expandCard();
},
child: AnimatedContainer(
height: cardExpanded ? null : 50,
duration: Duration(milliseconds: 200),
child: Card(
child: Text(aLotOfText),
),
)
)
expandCard() {
setState(() {
cardExpanded = !cardExpanded;
});
}
As you can see on the line height: cardExpanded ? null : 50, when cardExpanded is true I didn't specify the height, so in this way I'm able to expand the card to the right height to contain the text.
But with this little hack I've completely lost the animation and as long as this card would go in a listview with 20 or more other cards, the opening and closing makes the list to jump up and down.
I'm sure that there is a way to expand the card to the right height keeping also the animation.
I would also like to specify that the one above is just an example, in my card, there will be images and buttons as well.
In addition, when the card is opened with a button, I could add some text, so the card has to expand as well when it is opened.
That's why I cannot specify a height.
EDIT
With height: cardExpanded ? null : 50 I get what I want, so, the card grows big enough to fit the content, but as you can see there is no animation there
Instead if I provide a value like height: cardExpanded ? 100 : 50, I get the animation, but obviously the card grows until it gets to 100 pixels and it dosen't show all the content.
The result that I would like to obtain is what there is in the first example plus the animation.
Thankyou
You could set two variables:
height = MediaQuery.of(context).size.height * 0.8; //the percentage you want the card to grow
width = MediaQuery.of(context).size.width * 0.8;
So when expanded is true, the container grows.
height: cardExpanded ? height : 50,
width: cardExpanded ? width : 50,
Also, to specify how the animation will perform, you have a property that can make the animation look smoother.
curve: Curves.bounceInOut,
or you could try using a widget like expanded to adjust.
The design of an application I developed with Flutter is broken on devices with different screen sizes (tablet vs. phone). Cards and containers overlap and vary in size. What is your suggestion?
I really suggest you to give a look at the LayoutBuilder class that's been created exactly to solve your problem. Give a look at the doc for every info; here's a simple usage:
LayoutBuilder(
builder: (context, constraints) {
if (constraints.maxWidth < YOUR_SIZE)
return Column();
else
return GridView();
},
),
Very intuitively: if the width of the device is lower than YOUR_SIZE, the screen is not so "wide" and a column fits well. Otherwise you could place your widgets in a grid with N columns probably.
Official video about LayoutBuilder on YouTube.
Use widgets composition instead of functions that return Widget. Functions are NOT optimized, you can't const-construct the widget and they get rebuilt every time!
To make the size of any widget proportional with any device, you need to get the device width and height before setting widget size.
For instance, to create a container of size that is responsive to any device height and width and also scale when rotated, you need to use the MediaQuery to get the device height and width to scale you widget size as follows:
....
#override
Widget build(BuildContext context){
double _height = MediaQuery.of(context).size.height;
double _width = MediaQuery.of(context).size.width;
return Container(
height: _width > _height ? _width / 10 : _height / 10,
width: _width > _height? ? _height / 10 : _widht / 10,
child: AnyWidget(),
);
}
...
In iOS when I put marker and mapView location, the map does not display centered, but if I move the phone to landscape and rotate again to portrait the map center displays fine.
In Android works fine.
You can get more information in this GitHub issue:
https://github.com/dapriett/nativescript-google-maps-sdk/issues/322
I have personally faced this and discovered that it's basically a layout problem on iOS. The height and width attribute values supplied to the MapView's XML element are somewhat differently treated by iOS. The solution to our problem, as described in the question itself is resizing the map on runtime (as rotating the screen makes it go through a resizing routine). Applying this absurd logic at the beginning of the map render, solves the problem.
This is how I did it:
Provide the width value of the MapView in XML:
<maps:mapView width="100%" mapReady="onMapReady" />
and set the height of the map inside onMapReady method, with a 100 millisecond delay.
/* if you want to set height in DIP */
setTimeout(() => this.mapView.height = 500, 100);
or if you want to set height in percentage
/* [0.85 means 85% here] */
setTimeout(() => this.mapView.height = {
unit: '%',
value: 0.85
}, 100);
100 millisecond delay makes it go through the resize effect. Tested on iOS 12.1
I have a layout that has sum specifications when it is in portrait. But when I change the screen orientation the layout needs to reorganize to fit the entire screen. The problem is that the layout keeps he's dimensions. So if the layout starts portrait the I change to landscape the layout keeps the portrait configuration. How can I make my layout so it auto reorganizes on screen orientation changes?
How I create my views:
var deviceHeight = Ti.Platform.displayCaps.platformHeight,
deviceWidth = Ti.Platform.displayCaps.platformWidth,
platform = Ti.Platform.osname;
if (platform == 'android') {
deviceHeight = Math.round(Ti.Platform.displayCaps.platformHeight / Ti.Platform.displayCaps.logicalDensityFactor);
deviceWidth = Math.round(Ti.Platform.displayCaps.platformWidth / Ti.Platform.displayCaps.logicalDensityFactor);
}
var View = Ti.UI.createView({
height : deviceHeight,
width : deviceWidth,
backgroundColor : 'white',
layout : 'vertical'
});
Very important: Don't specify width in points/pixels, but in percentages. Or use relative width. For example, if you want a view that is full width, minus 10 left and 10 right, specify that:
Ti.UI.createView(){
left: 10,
right: 10
}
Treat Apps different as websites! Make everything relative. There are so many resolutions it is impossible to make a layout PER resolution. Make a single solution for mobile, and possible re-arrange some stuff for tablets.
If you really want to redraw manually, use this event and redefine all your views after again:
Ti.Gesture.addEventListener('orientationchange',function(e) {
});
For this to work, you need to keep a reference to all your views and adjust where you like.