WICG / aspect-ratio

This is a repo to explore, and hopefully define a way to maintain aspect ratio
19 stars 8 forks source link

Working on an aspect-ratio polyfill #7

Open tomhodgins opened 7 years ago

tomhodgins commented 7 years ago

Hi all, based on my comments in the other issue I have begun brainstorming and writing a polyfill for what an aspect-ratio property might look like. I currently have the logic figured out, and a regex that can extract this custom property from formatted CSS, I think I'm close to a working polyfill I just need to figure out how to list all CSS rules that affect a chosen element and determine if a width:; or height:; value has been assigned already in CSS before calculating the aspect ratio.

In thinking through how it should work, here's what I came up with:

Width Height Aspect-Ratio Calculated
height
height
width
nothing
✓ with !important nothing

This means any time there is both a width and height applied to the element in CSS, it would always take those values over the implied aspect-ratio, even if that aspect-ratio was marked !important.

For extracting values from formatted CSS. I have the following regex which will look for rules that contain -eq-aspect-ratio with the following syntax:

aspect ratio property = property name : width / height property name = -eq-aspect-ratio width = number with optional decimal and optional trailing digits height = number with optional decimal and optional trailing digits

So here's the regex that will extract this and give us: a CSS selector, the width value, the height value:

/^(.*){.*-eq-aspect-ratio:\s*(\d*\.?\d+)\/(\d*\.?\d+)/gm

And here's my test with formatted CSS using one rule per line and some other simple transformations JavaScript can apply to any CSS to get it ready for extraction: https://regex101.com/r/kSMEeT/1

I wrote an almost working plugin yesterday on the plane - I'm going to keep toying around with it until I can get it working (by checking not the computed styles, but what CSS properties apply to each given element) but it's looking really hopeful and came together a lot faster than I thought it would!

Any thoughts on the precedence I have set in the table above?

Any comments on the prefix I have chosen for development -eq-aspect-ratio?

Any ideas about how such a plugin should be structured?

I'll post my polyfill here once I get it working!

gregwhitworth commented 7 years ago

I think this sums up my desire from an aspect ratio in CSS in regards to layout. I think a polyfill is a good first step at ensuring it all works like one would expect. I'm still not sold on the need for 16/9 or 4/3 type aspect ratios though. But for now I think we can keep it this way.

gregwhitworth commented 7 years ago

@yoavweiss @zcorpan Do you all have any opinions on this - I know this aspect is firmly focused on the layout portion of aspect ratio but wanted to ensure you all are seeing this thread and if you have any opinions (eg: "this is preparser hostile" ;) ) on this before we go too far.

gregwhitworth commented 7 years ago

@tomhodgins Have you done any spec work before? I think what you have above is enough to begin a specification and you can build your polyfill off of that spec. I'm happy to lend a hand if you need it, or we can get on skype/google hangout if that would help out.

tomhodgins commented 7 years ago

Hey @gregwhitworth, when you say you're not sold on the need for 16/9 ratios or 4/3 do you mean that formatting with width/height as opposed to the calculated ratio in a value like 1.777 or 1.333, or do you mean the need for making elements match an aspect ratio?

Why I think the width/height ratio is nice is because most of the time a designer will be using this they will be aware of pixel dimensions of their video, image, or other content and may not be immediately aware that a video 513px x 384px is actually 4/3, but they could put 513/348 and be sure that no matter the actual resolution it shows up, the aspect ratio will be the same as the native resolution.

I'm hoping to have this working next week - I have to record a video before the end of this week so I won't have much more time to hack on it, but my current non-working WIP is at: http://codepen.io/tomhodgins/pen/YpGGVj?editors=1010

The parts I need to add are where the plugin lists all of the CSS rules that apply to an element in question and checks to see if there is a width:; or height:; applied to them, so it knows how to proceed with calculations, but this is the demo and demo HTML/CSS I will be building against!

tomhodgins commented 7 years ago

@gregwhitworth Never done any spec work, I've been trying for almost 2 years now to either contribute to the RICG, inspire action within the RICG, or write an element query spec without the RICG for element queries as well and research and create a syntax that works for that, from which all my aspect ratio demos have been coming from lately.

I'm passionate and ready to help out, I just don't know how to move things from descriptions of behaviour and definitions of syntax with working demos and plugins and turn that into a draft, spec, proposal, or anything like that.

For now I've been having a lot of fun using these ideas but I would like it if where was a way we could standardize some of this behaviour — even if browsers weren't going to support it right away or ever — so at least those of us polyfilling and wanting to build tools to use in the meantime to do these things could have a common target to implement so the tools/code can be more interoperable and to reduce technical debt for early adopters.

My gmail is tomhodgins@gmail.com and you can find me on skype as user innovati :D

gregwhitworth commented 7 years ago

I recommend we start with a shim - let's not worry about parsing out the CSS. This will make it much easier to design as you'll have to do this post layout anyways, mine as well not add the cost of parsing CSS in JS strings. I know shim doesn't sound as cool as polyfill - but it will be more performant and your code will be cleaner.

tomhodgins commented 7 years ago

@gregwhitworth I'm a JS learner so I'm writing it in the way that I know how to make it work. Doing it as a shim sounds…shimpossible at the moment, but I'm willing to try ^_^ Where could I learn how to write this as a shim?

gregwhitworth commented 7 years ago

I say, just do how you want it now - this is primarily for prototyping anyways, but if possible - abstract the logic as much away from the helper functions (CSS parsing, etc) as possible so that anyone looking at the logic can make sense of what is happening and comment on that and compare with the spec we write. This should be a pretty straight forward spec and polyfill so I wouldn't expect too much confusion or simple mistakes - but you never know :)

tomhodgins commented 7 years ago

Hey @gregwhitworth, by isolating the logic do you mean making a demo like this: http://codepen.io/tomhodgins/pen/VmmMdO

<style>
  div {
    margin: 1em;
    color: black;
    background: lime;
  }
</style>

<div data-ratio=16/9>Ratio only</div>
<div data-ratio=16/9 data-width=200>Width supplied</div>
<div data-ratio=16/9 data-height=200>Height Supplied</div>
<div data-ratio=16/9 data-width=200 data-height=200>Width &amp; Height Supplied</div>

<script>
  var tag = document.querySelectorAll('[data-ratio]')

  for (i in tag) {

    var ratio = tag[i].getAttribute('data-ratio'),
        width = tag[i].getAttribute('data-width'),
        height = tag[i].getAttribute('data-height')

    if (ratio) {
      var rWidth = ratio.split('/')[0],
          rHeight = ratio.split('/')[1]
    }

    // Ratio
    if (ratio && !width && !height) {
      tag[i].style.height = tag[i].offsetWidth / (rWidth/rHeight) + 'px'
    }

    // Ratio + Width
    if (ratio && width && !height) {
      tag[i].style.width = width + 'px'
      tag[i].style.height = width / (rWidth/rHeight) + 'px'
    }

    // Ratio + Height
    if (ratio && !width && height) {
      tag[i].style.height = height + 'px'
      tag[i].style.width = height * (rWidth/rHeight) + 'px'
    }

    // Ratio + Width + Height
    if (ratio && width && height) {
      tag[i].style.height = height + 'px'
      tag[i].style.width = width + 'px'
    }

  }
</script>
gregwhitworth commented 7 years ago

To some extent, I would do it a bit differently as you could still allow the width or height to be set via CSS and only set the aspect ratio stuff as data attr or javascript vars whatever method you prefer. Or, as you showed before you can go for the full polyfill, but put the helper functions in an external lib, Codepen is setup pretty good for this: https://blog.codepen.io/2013/05/28/new-feature-use-pens-as-external-resources/

tomhodgins commented 7 years ago

Got the polyfill working a little bit - now to test :)

Preview: http://staticresource.com/aspect-ratio.html

Source: https://gist.github.com/tomhodgins/d7e8a16c93e47613e5625878f73363d5

tomhodgins commented 7 years ago

FYI I've created a repository and started a spec with the information and behaviour mentioned in this thread: https://github.com/tomhodgins/aspect-ratio-spec

Here's a link to the work in progress: https://tomhodgins.github.io/aspect-ratio-spec/aspect-ratio.html

A syntax example: http://tomhodgins.github.io/aspect-ratio-spec/example/aspect-ratio.css

A JS plugin: http://tomhodgins.github.io/aspect-ratio-spec/plugin/aspect-ratio.js

And a demo page: http://tomhodgins.github.io/aspect-ratio-spec/demo.html

Hopefully this makes the work I've done so far more accessible as we're compiling a spec for this property 👍

yoavweiss commented 7 years ago

@gregwhitworth - I'm belatedly catching up with this. I have no objections from a preloader perspective and overall this seems ace 💃

potch commented 7 years ago

The one thing I've seen that helps is using calc(16 / 9) to avoid new unit syntax.

tomhodgins commented 7 years ago

@potch the ratio unit is already specified in CSS in Media Queries level 3 and 4

The <ratio> value type is a positive (not zero or negative) <integer> followed by optional whitespace, followed by a solidus ('/'), followed by optional whitespace, followed by a positive <integer>. <ratio>s can be ordered or compared by transforming them into the number obtained by dividing their first <integer> by their second <integer>.