How to force re-render after a WebKit 3D transform

2019-01-07 10:57发布

问题:

I'm using CSS 3D transformations to zoom a div, for example:

-webkit-transform: scale3d(2,2,1);

The scaling itself works fine in any WebKit browser. However, when using this on Safari (mobile or Windows), the content of the div is not re-rendered. The result is that the content gets blurred after scaling.

This effect only occurs when using 3D transformations. Everything works fine when using

-webkit-transform: scale(2);.

In order to exploit hardware acceleration on iPhone/iPad, it would be nice to use the 3D transformations.

Does anybody know how to tell Safari to re-render a div with the new scale?

回答1:

The reason why the text is blurry is because Webkit is treating the text as an image, I guess it's the price of being hardware accelerated. I'm assuming you are using transitions or animation keyframes in your ui, otherwise the performance gains are negligible and you should switch to non-3d transforms.

You can either:

• Add an eventlistener for transitionend and then replace the 3d transform for a standard transform, such as...

element.addEventListener("transitionend", function() {
  element.style.webkitTransform = 'scale(2,2)'
},false);

• Since Webkit treats stuff as an image, it's better to start big and scale down. So, write your css in your "end state" and scale it down for your normal state...

 #div {
  width: 200px; /*double of what you really need*/
  height: 200px; /*double of what you really need*/
  webkit-transform: scale3d(0.5, 0.5, 1);
}

 #div:hover {
  webkit-transform: scale3d(1, 1, 1);
}

And you get a crispy text on hover. I made a demo here (works on iOS too):

http://duopixel.com/stack/scale.html



回答2:

I found when trying to force a redraw of a div in safari for another reasons (recalculate text-overflow on hover), that is simple:

selector {
    /* your rules here */
}
selector:hover {
    /* your rules here */
}
selector:hover:after {
    content:"";
}

I did something on hover that changes the padding to accomodate some buttons, but in safari/chorme it doesnt recalculate the content correctly, adding the :after pseudoclass did the trick.

Note that I did not find this anywhere on the internet, I discovered it when fiddling.



回答3:

I'm trying to do the same thing. I think what's happening here is that Safari is just scaling pixels. That is, it does all its "normal" browser rendering and then scales the pixels of the result.

Example: Place a relatively high quality image (say 1000x1000 pixels) in a small div (200x200 pixels) and set the image to 100% width and height. When you 3D transform the div to scale 5 times, the result will be blurry in Safari and crisp in Chrome. Use a 2D transform and the image will appear crisp in both.

A workaround is to convert to a 2D transform after you are done with the 3D. However, I've found there is a slight delay when converting between transforms so this isn't working too well for me.



回答4:

I couldn't find a fix for making zoom-ins not blur in Safari (desktop v7.0.2 and the one included in iOS 6.1.3 and 7.0.6) but I did at some point, notice that I got a sharp png when I set the scale to 5. I don't know why, since that version of my code is lost in all the subsequent changes I made. All other scale factors were blurry.

Since the iPhone and iPad are target devices for this project I ended up abandoning scale transform and animating image height instead. I'm disappointed that the Safari team decided to implement transforms in a way that make them an unviable option in so many cases.



回答5:

Hmmm... I'm getting the same issue trying to scale up google maps images (hidpi) with Chrome 53.

The only solution I've found so far is to hide the image (or a div that contains the images) and then show it again. Can be with opacity=0 or visibility=hidden, but it actually has to be invisible for at least a frame or two.

This, BTW, isn't even a 3d transform. Just 2D stuff.



回答6:

Hmmm... I'm getting the same issue trying to scale up google maps images (hidpi) with Chrome 53.

One solution is to hide (opacity, visibility) the image for a few frames (or a container wrapping the image/images/whatever it is)... the better solution which I found in another post on SO was this (issued on the containing DIV):

e.style.transform = 'translateZ(0) scale(1.0, 1.0)'

BTW, my stuff was just ordinary 2d stuff, the translateZ seems to make the difference though, even though I never touched anything 3d.