background-size with background-position doesn't scale the position?
Asked Answered
C

6

16

I've a sprite of 62 images of 91 * 91px, which makes the whole thing 91 * 5642 px. They're displayed in sort of a dynamic grid that grows and shrinks depending on user/pointer movement. Sometimes an element (std 91 * 91 px) zooms in to make it 120 * 120 px. Obviously I want the background to grow with so that the entire 91 * 91 px image is shown in the entire 120 * 120 element.

Enter background-size: 100% auto to make the width always perfect. Problem now is that background-position expects its values to be updated as well! All 62 elements have inline style=background-position etc. I can't update the background position from inline. I want the background to first position and then resize (zoom), not resize and then position (zoom to wrong position).

I'm not sure I'm making any sense. To clarify somewhat:

  • All elements have a style of width: 91px; height: 91px; background-size: 100% auto;.
  • The second image would have an inline style of background-position: 0 -91px.
  • When you hover that element it gets a style width: 120px; height: 120px; and then it shows most part of the 2nd image and some part of the 1st, because positioning happens after resizing =(
  • If I change the background-position (after zoom/hover) to 0 -120px, it aligns correctly. (But then obviously it's wrong when not zooming/hovering.)

A very easy solution would be to use actual zoom: 1.3 or transform: scale(1.3), but that's VERY VERY slow with transitions.

I must be missing something. CSS has to be smarter than this. A sprite with background-size... That's not impossible is it!?

How the sprite looks is up to me, so I could make it have a 120 * 120 grid instead of 91 * 91, if that would be simpler...

EDIT: With example fiddle: http://jsfiddle.net/rudiedirkx/g4RQx/

Smart answer 1: background-position: 0 calc(100% / 61 * 2) (61 because 62 images, 2 because 3rd image)

Cleon answered 10/6, 2013 at 22:24 Comment(9)
SASS or LESS CSS preprocessing might help make this easierNonstandard
There are advantages to using sprite sheets in lots of situations but it would save a lot of headache to use individual images in this case. Is there any specific reason you need to use a sprite sheet?Gastrotrich
@DevinCrossman I don't think it does. Only making the sprite is easier. Preprocessors don't improve on CSS. If it's not possible in CSS, it's not possible with a preprocessor.Cleon
@3rror404 62 images... That would require a sprite IMO.Cleon
Check out the answer to this question #2430706 might be a possible solution?Nonstandard
@DevinCrossman There are 'solutions' with clipping (sort of), but not with background. Is CSS really this weird?Cleon
What transition are you using that is causing the use of transform: scale() to be "very very slow"? And when you say slow, do you mean low frame rate? What about using transform: scale3d() to force hardware acceleration?Eyecatching
Would you consider a solution that uses some jQuery?Fro
@MarcAudet Yes, if it doesn't repeat CSS logic (like how much it scales) and if it's rewritable in vanilla JS.Cleon
A
14

it's actually pretty simple, just use percentage position.

background-position: 0 3.28%;

http://jsfiddle.net/g4RQx/18/

Adenaadenauer answered 2/9, 2013 at 17:28 Comment(11)
@Cleon if you take this approach I suggest adding enough length (even if just empty space) to your image to allow for easy percentages - i.e. make it 100 * 91px long and your numbers are easy and not rounded.Tattoo
Great answer! this should get the bounty.Molar
Thx a lot @Dom. True fact Matthew, it's better to have rounded nunbers so you don't end up with a alien pixel line.Adenaadenauer
And agreed with @Molar - I think this can be the only solution without a per element class. +1 from me.Tattoo
@Adenaadenauer how did you calculate that percentage?Levity
did not.Just move to the right place than copy the value =D. But you could calculate the percentage (P%) easily since you have the size of the image (HI) and the size of the sprites (HS) and the position of the sprite you want (N). P% = 100 * N * HS / HIAdenaadenauer
The 62 is variable, but if I know -182px, I also know 6.56%. This is the smart answer I was expecting!Cleon
Even better: background-position: 0 -webkit-calc(100% / 62 * 0) jsfiddle.net/rudiedirkx/g4RQxCleon
background-position with % doesn't work =( because the % value doesn't mean top/left. It only means top/left if it's 0%. If it's 50%, it means center/center. If it's 100%, it means bottom/right. Damnit! Anything smart for that @rafaelcastrocouto?Cleon
I did. Thanks to you =) Fantastic!Cleon
All yours, @rafaelcastrocouto. Thanks!Cleon
L
5

In your hover state, edit your background position according to the scale ratio

a:hover {
    width: 120px;
    height: 120px;
    background-position: 0 -240px; /* -182px * 1.3186... */
}

FIDDLE


Just for the record: in your question you mentioned that scale is slow. I tried using scale and I didn't see slow transtions:

a:hover {
    transform: scale(1.3);
    transform-origin: 0 0;
    transition: all .5s;
}

FIDDLE

Levity answered 1/9, 2013 at 8:37 Comment(5)
The trouble with this is the position part is inline and set per image which is why you need a background-position setting per <a> element.Tattoo
Or - I should say at least that is how I understood it.Tattoo
Yes, Matthew, you understood. I'd need 62 different :hover states in CSS. Unacceptable.Cleon
@Levity I'll try that scale() again. It was very slow a year ago.Cleon
This should be a correct approach! This works great!Equinoctial
H
3

If your requirement is smoother transition than this is what you need.

DEMO

DEMO2 with centered zoom.

HTML:

 Hover image large transition<br>
    Hover that:<br>
    
    <p><a href="#"></a></p>
    <p><a href="#"></a></p>
    <p><a href="#"></a></p>
    <p><a href="#"></a></p>
    <p><a href="#"></a></p>

CSS:

 p {
    margin: 0;
    display: inline-block;
    
}
a {
    display: block;
    border: solid 10px green;
    /* always: */
    width: 91px;
    height: 91px;
    background: black url(//hotblocks.nl/tests/gamessprite.gif) no-repeat 0 0;
    background-size: 100% auto;

    /* inline: */
    background-position: 0 -182px;
     -webkit-transition: all .5s;
    -moz-transition: all .5s;
    -o-transition: all .5s;
    transition: all .5s;
}
a:hover {
    -webkit-transform: scale(1.3);
    -ms-transform: scale(1.3);
    transform: scale(1.3);
    -webkit-transform-origin: 0 0;
    -ms-transform-origin: 0 0;
    transform-origin: 0 0;
    -webkit-transition: all .5s;
    -moz-transition: all .5s;
    -o-transition: all .5s;
    transition: all .5s;
}

Fiddle: fiddle

Hebephrenia answered 6/9, 2013 at 7:17 Comment(2)
I suggest you to add translate3d(). This way you can use GPU acceleration. Source: mobile.smashingmagazine.com/2012/06/21/…Herta
I was looking for an answer without scale(). That wouldn't have required a background-position solution.Cleon
P
-1

Well ..., you have several problems. ;-)
Inline styles are "bad" in all ways. One is that you cannot override them with any external CSS (cause of their higher specificity). So this already one reason why there is no pure CSS solution for your problem.

Another reason is, that you would need to re-calculate the background-position depending on the actual position, respectively on the sprite number. Something you cannot do with CSS, yet (e.g. something like: background position: 0 calc(nth-child * 91px);)

So the only pure CCS way will be using the sale transform which also fairly good supported!

BTW: "How the sprite looks is up to me, so I could make it have a 120 * 120 grid instead of 91 * 91, if that would be simpler..."
That would be better at least. Downscaling is always better quality than upscaling!

A Javascript solution, especially with jQuery is quite simple on the opposite, as you can use the .index() function and so easily calculate the new background-position.

Conclusion:
At the moment there is no real/ practical solution for handling CSS sprites in such a way!

Pulverulent answered 2/9, 2013 at 15:27 Comment(3)
You/the client can override inline style with !important. I can't use jQuery's .index() because the element order isn't the sprite order. All source images are 91*91 =( so saving as 120 isn't very useful. transform: scale is indeed the simplest, but I've had very bad performance. (Last attempt worked well though.) I was mostly looking for bright new ideas. Have any?Cleon
OK, !important would be a possibility (personally I never use it) but as said before, it won't help because AFAIK there is no CSS solution. And about the JS thing ..., if the element order isn't the sprite order you could also use the current coordinate of the background image to calculate the new one."I was mostly looking for bright new ideas. Have any?" No, not really! Because imho it makes sense to use one graphic (instead of 60 or more). And unfortunately there is no other way than the scale transform to achieve your goal with CSS.Pulverulent
Source images are 91*91 which make 1 sprite. I meant. Agree on the transform: scale. Probably why it was invented too =) Maybe zoom: 1.3 works too though.... With negative margins... Oeh, will try.Cleon
O
-1

I think the answer should be simple,

I have updated the fiddle,

Please check it,

If you specify style like this, it should solve the problem easily...

a {
 display: block; /* always: */
 width: 91px;
 height: 91px;
 background: black url(//hotblocks.nl/tests/gamessprite.gif) no-repeat 0 0;
 background-size: 100% auto;
 /* inline: */
 background-position: 0% 3.30%; /* this will do the trick */ 
}
a:hover {
 width: 150px;
 height: 150px;
}

Please reply if any concern...

Opalina answered 3/9, 2013 at 6:39 Comment(0)
P
-1

Many of the answers suggests percentage instead of static pixels in the background-pos, i however have a different approach. It's a bit wierd... but it works! <-- fiddle

So i've added a span inside your link with the background attached to it. And instead of your inline background-position, ive instead used height in percent on the span, like this:

<a href="#">
    <span style="height: 100%"></span>
</a>

And with this css it will be all you need and the image will fix the rest.

a {
    display: block;
    overflow: hidden;
    width: 91px;
    height: 91px;
    position: relative;
}
a span{
    background: url(//hotblocks.nl/tests/gamessprite.gif) no-repeat 0 0;
    background-size: 100% auto;
    background-position: 0 0;
    position: absolute;
    display: block;
    bottom: 0;
    width: 100%;
}
a:hover {
    width: 120px;
    height: 120px;
}

This is more html and css that you need if you dont use the other solutions, but the more ways to solve something the better :)

Protozoan answered 3/9, 2013 at 14:47 Comment(1)
That's very smart! And would be totally acceptable if I have only ~ 10 tiles, but I have 62 and growing, so that will be HUGE <span>s and that's very expensive to render, even if they're only partially visible. It's very clever though =) I like.Cleon

© 2022 - 2024 — McMap. All rights reserved.