Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CSS: Dynamic classes / attributes and media queries - how to solve this dilemma?

Writing an extension for a Plugin I have the possibility to change all attributes of an HTML element using PHP.

$attributes["style"] .= 'padding-left:10px;';
array_push($attributes["class"], "long-container");
array_push($attributes["class"], "super smooth");
$attributes["data-whatever"] = "great";

Now I want to give a user the possibility to enter the width / height ratio of a div dynamically (the solution of how to do this is described in the answer by @Web_Designer here: Maintain the aspect ratio of a div with CSS).

Within the function where I can change the output of the third-party Plugin I wrote the following code for calculating the width height ratio according to the input. As the height of my boxes is :

if( !empty( $args['stretchy-desktop'] ) ) {
    $sd = array_map('trim',explode(":",$args['stretchy-desktop']));

    if(count($sd)==2) {
        $sd[0] = floatval(str_replace(",",".",$sd[0]));
        $sd[1] = floatval(str_replace(",",".",$sd[1]));

        if($sd[0]>0 && $sd[1]>0) {
            $padding = ($sd[1] / $sd[0])*100;
            array_push($attributes['class'], 'stretchy-desktop');
            $attributes['style'] .= 'padding-bottom:'.$padding.'%;';
        }
    }
}

Great right? However now the user wants a possibility to enter a different weight height ratio for mobile devices as well as a different dynamic min-height for mobile devices and this is there I am stuck.

1) It is not possible to give inline @media queries right now otherwise my solution would be like this (Is it possible to put CSS @media rules inline?):

$attributes['style'] .= '@media (min-width:540px) {padding-bottom:'.$padding.'%;}@media (max-width:539px) {padding-bottom:'.$padding_mobile.';}';

2) It is not possible to use HTML attribute values in CSS right now (CSS values using HTML5 data attribute) otherwise my solution would be like this:

$attributes['data-desktoppadding'] = $padding;
$attributes['data-mobilepadding'] = $padding_mobile;

In CSS:

@media (min-width:540px) {
.long-container {
padding-bottom: attr(data-desktoppadding);
}
}

@media (max-width:539px) {
.long-container {
padding-bottom: attr(data-mobilepadding);
}
}

3) As the values are dynamic numbers I can not define a CSS class for every possible existing float.

Of course I could use JavaScript but we all know the significant drawbacks (including ugly page load).

Can you think of any CSS solution for this dilemma?

like image 380
Blackbam Avatar asked Mar 23 '17 12:03

Blackbam


Video Answer


2 Answers

Here is a solution I came up with. It involves creating a wrapper div around the target element. Basically, the way that this works is that the outer div is assigned the inline styles for the mobile mode, and the inner div is assigned the inline styles for desktop mode. When the browser window is resized to be below the threshold for desktop view, it resets the inner div's (desktop) inline styles to defaults so the outer div's (mobile) inline styles take over. When the browser window is resized to be above the threshold, it resets the outer div's (mobile) inline styles to defaults so the inner div's (desktop) inline styles take over. The way that it overrides the inline styles is by using the !important keyword in the rulesets in the CSS media queries.

I think it goes without saying that the inline styles in the snippet below would be replaced with your $attributes['style']. But since you will have separate mobile and desktop styles, I guess it would be $attributes['style-mobile'] and $attributes['style-desktop'].

@media (min-width:540px) {
    .padding-mobile {
        padding-bottom:0 !important;
        width: auto !important;
    }
}
			
@media (max-width:539px) {
    .padding-desktop {
        padding-bottom:0 !important;
        width: auto !important;
    }
}
<div class="padding-mobile" style="width:100%;background-color:red;padding-bottom:100%;">
    <div class="padding-desktop" style="width:50%;background-color:red;padding-bottom:25%;">
				
				
    </div>
</div>
like image 61
Cave Johnson Avatar answered Oct 13 '22 08:10

Cave Johnson


An elegant approach that works in most major browsers is the usage of custom properties. They are basically variables in CSS. As of writing this (2017-03-27), only IE and Edge do not support this, although they are working on support for Edge.

You would add the variables to the $attributes['style'] and actually apply them in the stylesheet inside a media query. They are then used dynamically by the browser.

I have implemented a demo as a snippet, but because it is easier to change the viewport size on JSFiddle, also a copy of the demo there. Note that the responsive breakpoint is defined in CSS here, and the variables are defined in inline styles of the element.

.container {
  width: 200px;
}

.block {
  position: relative;
  display: block;
  box-sizing: border-box;
  width: 100%;
  height: 0;
  padding: 10px;
  padding-bottom: var(--desktop-ratio);
  background: #bada55;
  color: #444;
}

@media (min-width: 540px) {
  .mobile {
    display: none;
  }
}

@media (max-width: 539px) {
  .desktop {
    display: none;
  }
  
  .block {
    padding-bottom: var(--mobile-ratio);
  }
}
<div class="container">
  <div class="block" style="--desktop-ratio: 56.25%; --mobile-ratio: 200%;";>
  My aspect ratio is set via custom properties. It is <span class="mobile">1:2</span><span class="desktop">16:9</span>.
  </div>
</div>

It is (at least at the moment) apparently not possible to set a breakpoint using a variable. That is,

@media (min-width: var(--breakpoint)) { ... }

is apparently not understood by at least Firefox and Chrome at the moment.

 


 

Alternatively, building on Kodos Johnson's answer: if you have only a single breakpoint, you can make use of the padding-bottom and padding-top. One of the two defines the aspect ratio on small screens, and the other defines it on big screens. This removes the need to add a wrapper element. Here is an example, based on the one from Kodos' answer.

@media (min-width:540px) {
    .block {
        padding-top: 0 !important;
    }
}
			
@media (max-width:539px) {
    .block {
        padding-bottom: 0 !important;
    }
}
<div class="block" style="width: 50%;
                          padding-bottom: 25%;
                          padding-top: 100%;
                          background-color: red;">
</div>
like image 4
Just a student Avatar answered Oct 13 '22 08:10

Just a student