I am loading a background image and then adding different texts/shapes on the canvas.
I am trying to implement a functionality for the user to change the orientation of the whole canvas in fabric js, from portrait to landscape and vice-versa.
I am not restricted on the exact functionality, but I don't have too many ideas on how to do it.
Changing the transform property from CSS, then changing the angle of all objects in canvas, doesn't seem good. Transform property messes up some functionalities of fabric js (selection, moving, scaling, etc...)
Any other ideas on how to do it?
Related
Background
I'm working on a web app built in HTML (not WebGL/Canvas) which includes 2d viewport controls for panning and zooming the page content. Something like Figma, perhaps, but rendered entirely with DOM, which is a hard technical requirement.
To achieve the viewport functionality I've made extensive use of CSS transform to power all offsets and animations in order to reduce the work required to render changes to compositing, as much as possible. The "canvas" of my app contains many discrete items which can be moved and resized by the user, similar to any typical OS window manager. These 'widgets' may contain their own scrollable content.
For example, after panning 50px,25px and zooming to 1.5x, the DOM and transform values might look like this for a particular "canvas" which has a widget at (20, 100):
<div id="canvas" style="transform: scale(1.5, 1.5) translate(50px, 25px)">
<div id="widget-1" style="transform: translate(20px, 100px)" />
</div>
After a lot of experimentation I've discovered that the most efficient way to render these items across multiple browsers is to promote each individual 'widget' to its own layer by applying will-change: transform to the outermost element. This results in a pretty reasonable framerate, even with a lot of content in the frame while panning and zooming.
Webkit Misbehavior
However, there's one catch - on Webkit-based browsers, when zooming (which is applied via scale transform in CSS on the root canvas element), the contents of the widgets are not re-rasterized to accommodate the new scale value. At a zoom greater than 1x, this produces noticeable blurriness. Images with text, in particular, are basically unreadable.
Above, one image widget and another DOM text widget at 1x (native) scale.
And now, at 2x scale (you won't be able to tell the difference inline in this post, but you can see it at full resolution). Notice that the image is just as illegible as before, and the text is blurry.
For a live reproduction of this problem, see this CodeSandbox (leave "Animation" unchecked).
Side note: this only happens on Chrome, Safari, and Edge - so it seems like an artifact of Webkit's rendering behavior. Firefox actually scales everything quite nicely, and with a faster framerate to boot.
However, the performance of this approach is desirable. After trying some other configurations of layering, I decided the best approach would be to try to force the browser to re-rasterize the widgets once a zoom change animation was completed.
The Hack
The intended goal is to allow the old rasterized textures to persist during the zoom animation to make it as smooth as possible (so the blurriness seen above will be present while the viewport scales up/down), but to trigger a re-rasterization at the final scale once the animation is complete - re-draw all the widgets, taking current scale into account so that their contents are sharp and legible even at 2x zoom.
To correct this problem, I have implemented what feels like a "hack": after the end of each zoom animation, I'm toggling the will-change on all the widgets from transform to initial for 1 frame, and then back again:
const rerasterize = () => {
requestAnimationFrame(() => {
element.style.willChange = 'initial';
requestAnimationFrame(() => {
element.style.willChange = 'transform';
});
});
};
This actually works. Now when I zoom in to 2x, the image text is legible and the DOM text is nice and sharp:
(In my app, you can even see the moment when the zoom animation "settles" and text "pops" into high-resolution.)
However, if I understand correctly, the code above is actually forcing the browser to dispose of the composite layer for each widget for 1 frame, and then recreate it. While the performance seems acceptable in practice, I would much prefer to just ask the browser to invalidate the layer which it has already constructed.
The Question
So, with all that context aside, the question is simply: is there a way to manually trigger an invalidation of a composite layer without trashing it? A magic CSS incantation, perhaps?
I'm also open to alternative approaches with respect to layer grouping which might improve behavior without harming render performance.
Other Stuff I Tried
One thing I noticed when creating the reproduction CodeSandbox is that if I add a transition property to the "canvas" element (which is being transformed to achieve the viewport changes), even if widgets are composited in different layers, it appears to fix the blurriness. You can see this by checking "Animation" in the demo. However, my animations are currently done via JS, so adding a secondary CSS transition on top of this doesn't seem like a great plan.
I tried ripping out JS animations entirely and relying solely on transition, but surprisingly this did not seem to help. Panning and zooming felt noticeably choppier (some of this might come down to native easing feeling less natural than JS spring-based easing), but more concerningly the GPU memory usage and dropped frames were notably worse than without transition - which leads me to believe that transition might be causing a lot more work than I really want on the GPU for my use case (perhaps invalidating layers frequently during animations, when I would prefer them to remain intact until the transition ends).
I'd like to use zoom property in CSS for images but would not like to have a blurry effect to enlarged images but rather pixelated. How can I achieve this?
edit: if neccesary, it is ok to use other properties or other languages.
You cannot (currently) use upscaling and specify that the browser should use nearest-neighbor zooming, neither with HTML images, upsized HTML5 canvases, using drawImage() on a canvas to draw the image larger, nor zooming up images on SVG.
Here's a solution (written for this related question) showing how to 'zoom' an image using nearest-neighbor pixelation by re-creating the image as rectangles in SVG: http://phrogz.net/tmp/canvas_image_zoom_svg.xhtml
Here's another solution showing how to achieve this same effect using HTML5 Canvas (drawing rectangles on the canvas for the zoom):
http://phrogz.net/tmp/canvas_image_zoom.html
You might want to try a lightweight jQuery plugin called Zoomoz. The settings include easing and native animation too.
I have a conceptual question about photo galleries like this:
http://www.nikesh.me/demo/image-hover.html
If you open this in a browser that supports CSS Transitions (for example Chrome), it will smoothly scale the hovered image whilst the zoomed version remains of a high quality.
This is accomplished by showing the non-zoomed images into a slightly smaller version than they really are, in essence the zoom shows them in their true dimensions.
So, normal images are first scaled down:
-webkit-transform:scale(0.8);
And then upon hover scaled up:
-webkit-transform:scale(1.2);
My question: How is the initial scaling down supposed to work for browsers that do not support this method of scaling down? Try opening that gallery in IE to see what I mean, it shows the images not scaled down, which makes them too large and thereby they break the layout.
What I want:
The full effect in browsers that support it. Important is that the zoomed version remains quality.
No effect at all for browsers that do not support it, yet a solid original dimension so that no layout is broken
It should work for both image orientations and there may be variety in image widths and heights too
Anyone? Preferably an elegant solution that does not need browser sniffing or javascript, but all answers are welcome.
If you are wanting it to work without the use of javascript then it seems the only method you have is to forgo the initial scale down with css. You will want to do this in the "antiquated" way of adjusting the width and height of the image in the markup.
<img src="yourImageSrc" width="80%" height="80%">
This would allow you to still keep your layout in tact if the user agent is not up to date.
** I don't know if the percentage works in the literal height/width definition. But you can always figure out the ratio you need and plug it in.
I am creating Sprite objects as simple shapes, and would like to know how to resize them dynamically.
my question:
How can I enable the Sprite to be re-sized on mouse drag(perhaps enabling a only a portion of the Sprite for this behavior)?
It might be important to note that I am using the Flex SDK, and therefore do not have full
access to the Flash libraries.
Thank you in advance.
You might want to check out the Flex Object Handles open source library. Check out the demo to see how it can be used to allow users to resize any object in a Flex app.
Doesn't the sprite always adapt itself to its contents to set its height and width? (like movieclips in as2). If you want to resize a sprite, I guess you'd have to draw something as background?
What do you mean by enabling a portion of the sprite to be re-sized?
I have a Flex component with a background image. The image is sharp in the beginning, but is jagged whenever I scale the component using scaleX and scaleY. How would I make the image anti-alias so that, it it's scaled to 0.75, the lines are smooth, not jaggedy?
Here is the image
Here is the scaled version
And the unscaled (good) one
If you load the image with an Image component, you can cast the content property of the component to a Bitmap and then set smoothing to true. Unfortunately, the image component doesn't provide this functionality out of the box. However, it's rather easy to hack in.
Here is a tutorial to show you how to create such a component:
http://www.adobe.com/cfusion/communityengine/index.cfm?event=showdetails&productId=2&postId=4001
However, if this is set using the backgroundImage style of a component, you just might be out of luck unless you override updateDisplayList and perform the drawing of the bitmap yourself by using Graphics.beginBitmapFill (which does provide smoothing support).
Why smoothing of images doesn't have better support (such as different interpolation methods) in Flex (and subsequently, Flash) boggles my mind. At least pixel bender filters will help a bit by letting us implement such filters ourselves.
If the dimensions of your Bitmap are both either n^2 or n^8, then the Flash player will automatically use a technique called mip-mapping that will dramatically improve the look (and performance) of scaled bitmap images.