10

I'm scaling a div up with the transform property, but I want to keep its children (which have 1px width or height) the same size. I counter-scaled them by .5, with the expected result that an element of 1px scaled by 2, and then .5, should end up back at 1px, but they wind up a blurry 2px.

Here's the box before scaling it:

.container {
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: #EEE;
    position: absolute;
}

.outline {
    position: absolute;
    background: #1899ef;
    z-index: 999999;
    opacity: 1 !important;
}

.outlineBottom, .outlineTop {
  width: 100%;
  height: 1px;
}

.outlineLeft, .outlineRight {
    height: 100%;
    width: 1px;
}

.outlineRight {
    right: 0px;
}

.outlineBottom {
    bottom: 0px;
}
<div class="container">
    <div class="outline outlineTop"></div>
    <div class="outline outlineRight"></div>
    <div class="outline outlineBottom"></div>
    <div class="outline outlineLeft"></div>
</div>

As you can see, the elements at the edges are a clear, dark 1px blue. Here's what the box looks like after scaling, though:

.container {
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: #EEE;
    position: absolute;
    transform: scale(2);
}

.outline {
    position: absolute;
    background: #1899ef;
    z-index: 999999;
    opacity: 1 !important;
    transform: scale(.5);
}

.outlineBottom, .outlineTop {
  width: 100%;
  height: 1px;
  transform: scale(1,.5);
}

.outlineLeft, .outlineRight {
    height: 100%;
    width: 1px;
    transform: scale(.5,1);
}

.outlineRight {
    right: 0px;
}

.outlineBottom {
    bottom: 0px;
}
<div class="container">
    <div class="outline outlineTop"></div>
    <div class="outline outlineRight"></div>
    <div class="outline outlineBottom"></div>
    <div class="outline outlineLeft"></div>
</div>

And here's a post-scaled render from Chrome 41.0.2272.89 Mac, which is what I'm running.

Adding transform-3d(0, 0, 0) didn't appear to help. A solution was found using the zoom property, but since zoom isn't well supported I'd like to avoid that. Adding filter: blur(0px); didn't appear to have any effect either.

It was posited in chat that perhaps the children are first scaled to .5 and then doubled in size, causing them to be scaled down to .5px and then back up from there. Is there any way to ensure the order that they're rendered in causes them to first be scaled up to 2px and then halved? Against my better judgement, I tried forcing the render order with JS, but unsurprisingly, that didn't have any effect (though, interestingly, the bottom element did maintain its original color).

Failing that, are there any other solutions floating around out there? I can't be the only one who's run into this problem.

6
  • Any chance you can show a before / after? It is unclear what the issue is Commented Mar 13, 2015 at 16:31
  • Before / after scaling? Not sure I understand what you're asking. Commented Mar 13, 2015 at 16:32
  • Yes, before and after the scaling. I don't really see any issue with the box that you currently have - but that's probably because I am unclear of the desired effect Commented Mar 13, 2015 at 16:34
  • @Adjit Hopefully that should clear things up for you. Commented Mar 13, 2015 at 16:41
  • I'm glad you found the solution finally, and it was so close (and yet so far :P) from the tests yesterday Commented Mar 13, 2015 at 19:32

1 Answer 1

12

It is to do with the default transform-origin on the scaled elements. It defaults to 50% 50% for any element being transformed, but this has issues when scaling down 1px values as it has to centre the scale on a half pixel and the rendering of the elements has issues from here on out. You can see it working here with the transform-origin moved to the relevant extremes for each item.

A bit of playing about shows that this same blurring happens on scaled elements for any dimension where the scaling ends up halving a pixel.

body {
    padding: 1em;
}

.container {
    width: 200px;
    height: 200px;
    margin: 100px;
    background-color: #EEE;
    position: absolute;
    transform: scale(2);
}

.outline {
    position: absolute;
    background: #1899ef;
    z-index: 999999;
    opacity: 1 !important;
}

.outlineBottom, .outlineTop {
    width: 100%;
    height: 1px;
    transform: scale(1, 0.5);
}

.outlineBottom {
    bottom: 0;
    transform-origin: 0 100%;
}

.outlineTop {
    transform-origin: 0 0;
}

.outlineLeft, .outlineRight {
    height: 100%;
    width: 1px;
    transform: scale(.5,1);
}

.outlineRight {
    right: 0px;
    transform-origin: 100% 0;
}

.outlineLeft {
    left: 0px;
    transform-origin: 0 0;
}
<div class="container">
    <div class="outline outlineTop"></div>
    <div class="outline outlineRight"></div>
    <div class="outline outlineBottom"></div>
    <div class="outline outlineLeft"></div>
</div>

Sign up to request clarification or add additional context in comments.

3 Comments

No worries, I'd never seen this issue before but with a bit of playing it sort of makes sense that this would happen ... a feature rather than a bug I think, as you wouldn't want transform origins to be guessed in these sort of situations!
This is a really clean solution. And so simple :)
Could you elaborate a bit on what the actual solution is? I see that you're setting transform-origin to different values, depending on the element, but I don't quite understand the idea behind it.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.