Ryan O'Rourke
Ryan O'Rourke

Reputation: 1871

Blurry text after using CSS transform: scale(); in Chrome

Seems like there has been a recent update to Google Chrome that causes blurry text after doing a transform: scale(). Specifically I'm doing this:

@-webkit-keyframes bounceIn {
  0% {
    opacity: 0;
    -webkit-transform: scale(.3);
  }

  50% {
    opacity: 1;
    -webkit-transform: scale(1.05);
  }

  70% {
    -webkit-transform: scale(.9);
  }

  100% {
    -webkit-transform: scale(1);
  }
}

If you visit http://rourkery.com in Chrome, you should see the problem on the main text area. It didn't used to do this and it doesn't seem to effect other webkit browsers (like Safari). There were some other posts about people experiencing a similar issue with 3d transforms, but can't find anything about 2d transforms like this.

Any ideas would be appreciated, thanks!

Upvotes: 187

Views: 263172

Answers (30)

Eigil
Eigil

Reputation: 101

I tried all the css suggestions without any luck. After some experimentation I found that if you replace the html element directly under the element being scaled with itself everything is crisp. Here is a react hook example:

const useChromeScaleHack = (ref: MutableRefObject<HTMLElement>, scale: number) => {
    if (!window.chrome) return

    useEffect(() => {
        if (scale > 1.09 && ref.current) {
            const el = ref.current
            el.replaceWith(el)
        }
    }, [scale])
}

const Comp = ({ scale, children }) => {
    const ref = useRef(null)
    useChromeScaleHack(ref, scale)
    return (
        <div
            style={{
                willChange: "transform",
                transform: `scale(${scale})`,
            }}
        >
            <div ref={ref}>{children}</div>
        </div>
    )
}

Upvotes: 2

f0rmat1k
f0rmat1k

Reputation: 55

In many cases trouble blurry text caused videocard painting (in reason of opacity+animation). And even when animation is ended, videocard still painting block and text is blurry. To avoid it you need:

  1. Change animation to transition
  2. Manual control opacity:
    • default opacity: 0 in css file or inline
    • Manual set style opacity 1
    • onTransitionEnd remove inline style opacity

Upvotes: 0

Lucas Santana
Lucas Santana

Reputation: 1

2024 answer here !

I tried all the options posted here, but I didn't succeed.

Here's my solution with javascript:

const myDiv = document.querySelector('.my-div');
const divHeight = myDiv.offsetHeight - 1;
myDiv.style.height = modalHeight + 'px';

I found that setting a height in CSS worked just fine but my case is to height always be set as "auto", so I use js to get the current height from my div and subtract by 1.

Worked like a charm!

Upvotes: 0

Shimsho
Shimsho

Reputation: 259

For anyone experiencing this issue still in 2024, none of the forementioned issues will probably work across all devices etc. It happens because it rasterizes the element and scales it from the height it rasterized at.

To fix this the solution I created was creating a wrapper with transform: scale(2, 2) & pointer-events: none then on the inner element set the transform: scale(0.5, 0.5) and pointer-events: all, then you can adjust your hover scaling respective of the inner element scale. This will rasterize your element 2x the natural scale, making it a higher quality. If your element needs to scale above 2 adjust your wrapper scale accordingly.

.wrapper {
   transform: scale(2, 2);
   pointer-events: none;
}

.element {
   transform: scale(0.5, 0.5);
   pointer-events: all;
}

.element:hover {
   transform: scale(1, 1);
}
<div className="wrapper">
   <div className="element" />
</div>

Upvotes: 0

Vlad Dobrinov
Vlad Dobrinov

Reputation: 256

highest rated solution works but not for <h1...6> tags, had to specify font-size for them to work.

Upvotes: 0

WuChaoZhi
WuChaoZhi

Reputation: 1

For me, my solution had a bit of luck involved. My solution only works in certain scenarios. My browser is chrome 120.0.6099.110. I solved this problem by adding

content-visable:auto 

to the text wrapping layer. I have tried all the above-mentioned upvoted answers, but to no avail. My interface is a table. For product comparison, the transform:scaleY(-1) element needs to be flipped and reduced in the vertical direction, that is, upside down. When there are more than 8 products compared, the DOM content of the page will be very large, with many columns and rows of data, then there will be problems with Chrome rendering, and the problem of blurred fonts will occur. By adding content-visable:auto, the browser will reduce some rendering content, this is my understanding. I have a horizontal scroll bar to drag and display all the content, because one screen cannot display everything. No problem dragging, great content-viable

Upvotes: -1

Hakan
Hakan

Reputation: 613

Try adding this rule seperately to transformed element. both are working:

border:0 !important;
or 
border-radius:0 !important;

Upvotes: 0

ykadaru
ykadaru

Reputation: 1144

To improve the blurriness, esp. on Chrome, try doing this:

transform: perspective(1px) translateZ(0);
backface-visibility: hidden;

Perspective adds distance between the user and the z-plane, which technically scales the object, making the blurriness seem 'permanent'. The perspective(1px) above is like duck-tape because we're matching the blurriness we're trying to solve. You might have better luck with the css below:

transform: translateZ(0);
backface-visibility: hidden;

Upvotes: 34

2ne
2ne

Reputation: 5996

I have had this problem a number of times and there seems to be 2 ways of fixing it (shown below). You can use either of these properties to fix the rendering, or both at the same time.

Backface visibility hidden fixes the problem as it simplifies the animation to just the front of the object, whereas the default state is the front and the back.

backface-visibility: hidden;

TranslateZ also works as it is a hack to add hardware acceleration to the animation.

transform: translateZ(0);

Both of these properties fix the problem that you are having but some people also like to add

-webkit-font-smoothing: subpixel-antialiased;

to their animated object. I find that it can change the rendering of a web font but feel free to experiment with that method too.

Upvotes: 123

bilaltehseen
bilaltehseen

Reputation: 101

I was using box-shadow on my card and that is why it was causing me trouble after removing that box-shadow i was able to see crisp text after scaling.

Upvotes: 0

Daniel Fernandes
Daniel Fernandes

Reputation: 1

What worked for me was to remove backdrop-filter: blur(8px);. While this filter was only being applied to the background of a <div>, and not to its text, it seems like merely the presence of this property causes Chrome to slightly blur the text when used with transform: scale();.

Upvotes: 0

Roman Reznikov
Roman Reznikov

Reputation: 11

Try to play with zoom value on the animated block. In my case, zoom: 99.6%; totally fixes the blurry text. But for example, with 99.7% value text is still blurry, so in each case this value can vary.

Upvotes: 0

Craig Poole
Craig Poole

Reputation: 750

I have a div that has a small perspective shift on it to give a subtle 3D effect. The text in the div was blurring and I tried all the suggestions here to no avail.

Oddly, I found that setting 'filter: inherit;' on the text elements vastly improved the clarity. Though I can't understand why.

Here's my code in case it helps:

Html:

<div id="NavContainer">
    <div id="Nav">
        <label>Title</label>
        <nav>
            <a href="/">home</a>
            <a href="/link1">link1</a>
            <a href="/link2">link2</a>
        </nav>
    </div>
</div>

Css:

    #NavContainer {
        position: absolute;
        z-index: 1;
        top: 0;
        left: 20px;
        right: 20px;
        perspective: 80vw;
        perspective-origin: top center;
    }

    #Nav {
        text-align: right;
        transform: rotateX(-5deg);
    }

        #Nav > nav > a,
        #Nav > label {
            display: inline-block;
            filter: inherit;
        }

        #Nav > label {
            float: left;
            font-weight: bold;
        }

Upvotes: 1

Harsh Mittal
Harsh Mittal

Reputation: 346

This is what worked for me:

body { perspective: 1px; }

Upvotes: 2

So. I tried all the solutions above and nothing really worked. But!

I have a modal-root div in my index.html of my React App. I am rendering a Modal component (.modal) in it if it is necessary. First I positioned the modal itself fixed, making it top-left 50% and applied a transition(-50%, -50%) to get it centered.

I zoomed in-and-out and noticed the blurriness of the modal content if the zoom ratio of the Chrome browser was not 100%. It could be 110% or 90-80-75 etc. percent, doesn't matter. Apart from 100% zoom it was really blurry and ugly.

So I decided to get rid of the entire transition-based solution that I had before to center the child of the .modal element.

I am positioning my modal-root fixed, making it top-left-right-bottom 0, so it is always 100vh and wv. Then I made it a flex-container and positioned its child via align-items and justify-content centered.

But! There is always a but. The modal-root has a z-index: -1 on default. I decided to change it programmatically to 59 if the modal is opened. I also apply an overlay that makes the rest of the page darker, that is the .overlay which has a z-index of 60. The actual modal content (.modal) has a z-index of 61.

I also wanted to animate the entrance of that element when it appears so I am playing around with the margins, instead of applying any kind of transition to it. It's a cubic-bazier animation that handles that margin-top at the fitting percent within the animation but at the end it is margin-top 0 with no transition property.

It was a bit pity to have to rework the component but after testing it it works pretty fine.

Summary:

  • Highest parent container fixed, covering the entire page
  • Setting the highest parentcontainer's z-index -1 or [custom-value] depending on wanting to interacting with the modal (modal is open)
  • Setting the container's z-index to -1 if the modal is closed
  • Making the container a flex-container and position its child to the center
  • Animating the child via margin values instead of transition

I hope it can be helpful for some of you folks :)

Upvotes: 2

toljoas
toljoas

Reputation: 37

For anyone reading in the future: In my case the issue was that I added:

perspective: 2500px;

To a parent element. Removing this property resolved the issue.

Upvotes: 0

Urs
Urs

Reputation: 5132

In my case, the mistake was that the animation was set to a parent div of the animated element (an SVG). The parent div had some crazy width, like 466.667px. Setting that to an odd number wouldn't help. But: targetting the SVG itself with the animation (instead of the parent container did).

Upvotes: 0

user1769038
user1769038

Reputation: 33

Just to add to the fix craze, putting {border:1px solid #???} around the badly looking object fixes the issue for me. In case you have a stable background colour, consider this too. This is so dumb noone thought about mentioning I guess, eh eh.

Upvotes: 0

Oleg Bondarenko
Oleg Bondarenko

Reputation: 1830

I have tried a lot of examples from these answers unfortunately nothing help for Chrome Version 81.0.4044.138 I have added to transforming element instead

 transform-origin: 50% 50%;

this one

 transform-origin: 51% 51%;

it helps for me

Upvotes: 2

madstone
madstone

Reputation: 1

It will be difficult to solve with only css.

So I solved it with jquery.

This is my CSS.

.trY {
   top: 50%;
   transform: translateY(-50%);
}

.trX {
   left: 50%;
   transform: translateX(-50%);
}

.trXY {
   top: 50%;
   left: 50%;
   transform: translate(-50%, -50%);
}

and this is my jquery.

function tr_init() {
$(".trY, .trX, .trXY").each(function () {
    if ($(this).outerWidth() % 2 != 0) {
        var fixed_width = Math.ceil($(this).outerWidth() / 2) * 2;
        $(this).css("width", fixed_width);
    }
    if ($(this).outerHeight() % 2 != 0) {
        var fixed_height = Math.ceil($(this).outerHeight() / 2) * 2;
        $(this).css("height", fixed_height);
    }
})}

Upvotes: 0

Prajwal_Shenoy
Prajwal_Shenoy

Reputation: 41

I was facing the blurry text issue on Chrome but not on Firefox when I used transform: translate(-50%,-50%).

Well, I really tried a lot of workarounds like:

transform: perspective(1px);
filter: blur(0);
transform: translateZ(0);
backface-visibility: hidden;

None of these worked to me.

Finally, I made the height and width of the element even. It resolved the issue for me!!!

Note: It might depend from use case to use case. But surely worth a try!

Upvotes: 2

user9164129
user9164129

Reputation:

My solution was:

display: initial;

Then it was crispy sharp

Upvotes: 2

Ravi
Ravi

Reputation: 51

I have this same problem. I fixed this using:

.element {
  display: table
}

Upvotes: 5

SJacks
SJacks

Reputation: 438

2019 Update
The Chrome display bug is still unfixed and though no fault of the patrons, none of the suggestions offered in the entirety of this website help to resolve the issue. I can concur that I have tried every single one of them in vain: only 1 comes close and that's the css rule: filter:blur(0); which eliminates the shifting of a container by 1px but does not resolve the blurred display bug of the container itself and any content it may have.

Here's the reality: there literally is no fix to this problem so here is a work around for fluid websites

CASE
I'm currently developing a fluid website and have 3 divs, all centered with hover effects and sharing percentage values in both the width and position. The Chrome bug occurs on the center container which is set to left:50%; and transform:translateX(-50%); a common setting.

EXAMPLE: First the HTML...

<div id="box1" class="box">
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry"s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
</div>

<div id="box2" class="box">
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry"s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
</div>

<div id="box3" class="box">
    Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry"s standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.
</div>

Here's the CSS where the Chrome bug occurs...

*{margin:0; padding:0; border:0; outline:0; box-sizing:border-box;  background:#505050;}
.box {position:absolute; border:1px solid #fff; border-radius:10px; width:26%; background:#8e1515; padding:25px; top:20px; font-size:12pt; color:#fff; overflow:hidden; text-align:center; transition:0.5s ease-in-out;}
.box:hover {background:#191616;}
.box:active {background:#191616;}
.box:focus {background:#191616;}
#box1 {left:5%;}
#box2 {left:50%; transform:translateX(-50%);} /* Bugged */
#box3 {right:5%;}

Here's the fixed css...

*{margin:0; padding:0; border:0; outline:0; box-sizing:border-box;  background:#505050;}
.box {position:absolute; border:1px solid #fff; border-radius:10px; width:26%; background:#8e1515; padding:25px; top:20px; font-size:12pt; color:#fff; overflow:hidden; text-align:center; transition:0.5s ease-in-out;}
.box:hover {background:#191616;}
.box:active {background:#191616;}
.box:focus {background:#191616;}
#box1 {left:5%;}
#box2 {left:37%;} /* Fixed */
#box3 {right:5%;}

Bugged fiddle: https://jsfiddle.net/m9bgrunx/2/
Fixed fiddle: https://jsfiddle.net/uoc6e2dm/2/

As you can see a small amount of tweaking to the CSS should reduce or eliminate the requirement to use transform for positioning. This could also apply to fixed width websites as well as fluid.

Upvotes: 5

Kyle Underhill
Kyle Underhill

Reputation: 109

I used a combination of all answers and this is what worked for me in the end:

.modal .modal--transition {
  display: inline-table;
  transform: perspective(1px) scale(1) translateZ(0);
  backface-visibility: hidden;
  -webkit-font-smoothing: subpixel-antialiased;
}

Upvotes: 2

Austin
Austin

Reputation: 117

In Chrome 74.0.3729.169, current as of 5-25-19, there doesn't seem to be any fix for blurring occurring at certain browser zoom levels caused by the transform. Even a simple TransformY(50px) will blur the element. This doesn't occur in current versions of Firefox, Edge or Safari, and it doesn't seem to occur at all zoom levels.

Upvotes: 1

Corentin
Corentin

Reputation: 149

Another fix to try i just found for blurry transforms (translate3d, scaleX) on Chrome is to set the element as "display: inline-table;". It seems to force pixel rounding in some case (on the X axis).

I read subpixel positioning under Chrome was intended and devs won't fix it.

Upvotes: 4

Jonas Borneland
Jonas Borneland

Reputation: 413

FOR CHORME:

I´ve tried all suggestions here. But diden't work. My college found a great solution, that works better:

You should NOT scale past 1.0

And include translateZ(0) in the hover but NOT in the none-hover/initial position.

Example:

a {
    transition: all 500ms cubic-bezier(0.165, 0.840, 0.440, 1.000);
    transform: scale(0.8, 0.8);
}

a:hover {
    transform: translateZ(0)scale(1.0, 1.0);
}

Upvotes: 1

Tanha Islam
Tanha Islam

Reputation: 41

I removed this from my code - transform-style: preserve-3d; and added this- transform: perspective(1px) translateZ(0);

the blur went away!

Upvotes: 1

Dan
Dan

Reputation: 751

After trying everything else here with no luck, what finally fixed this issue for me was removing the will-change: transform; property. For some reason it caused horribly blurry looking scaling in Chrome, but not Firefox.

Upvotes: 64

Related Questions