I have created a jsFiddle to demonstrate this issue. It's just an example.
What I'm doing
Let's say I'm making a flexible grid. My HTML looks like this:
<div>
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</div>
Four columns. I have two mixins and a global variable called $gutter. In my mixin, I call this variable to add gutters and change the widths.
$gutter: 1%;
#mixin col($width){
float: left;
width: $width - ($gutter * 2);
margin: 0 $gutter;
}
#mixin row(){
width: 100%;
overflow: hidden;
}
I use it like so:
div { #include row(); }
p { #include col(25%); }
What I want to do
Now let's say I want to add a second, different grid to the page. I create this HTML and give each grid and ID to differentiate them:
<div id="one">
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</div>
<div id="two">
<p>a</p>
<p>b</p>
<p>c</p>
<p>d</p>
</div>
I want the second grid to have a different gutter width. Or, alternatively, no gutters.
#one { #include row(); }
#two { #include row($gutter: 0); }
This obviously does not work. Because the number of columns can be variable, I cannot add this $gutter:0 declaration to each instance of #include col(). It breaks the DRY principle and eventually (in complicated layouts) becomes unmaintainable.
The question
How can I allow a variable set in one mixin to filter down to another (on a child element)? I am aware that I could simply do this:
#mixin row(){
width: 100%;
overflow: hidden;
.col { etc etc etc }
}
But the class name may not always be .col. Does this make sense? I want the col() mixin to inherit a variable I pass through to the row() mixin. How?
I forked the jsfiddle from Sófka and extended it with the CSS Child selector (">") to target any tags in the row.
// Inside the row mixin
& > * {
#include col($columnWidth, $gutter);
}
Furthermore I added the column width attribute to the mixin and set a default gutter.
$defaultGutter: 1% !default; // Make sure the variable is set
#mixin col($width, $gutter: $defaultGutter){
...
}
See: http://jsfiddle.net/N44LW/12/
PS.:I'm not sure if I fully understood your question, but hopefully this helps.
Related
I'm curious to the behaviour of using calc() in setting a CSS variable.
Example:
#test {
--halfWidth: calc(100% / 2);
}
Now, if the #test element, say a div, was 500px wide, I would like the --halfWidth variable to be set to 250px.
But, as far as I can tell the var(--halfWidth) code used elsewhere simply drops in the calc(100% / 2) string instead of 250px. Which means that I can't use the calculation of say element A and use it in element B later on, since it would simply set for example width: var(--halfWidth); as half the width of element B instead of half the width of element A, where the variable was defined.
I've scoured the web trying to find any documentation on the behaviour of this, but I have so far drawn a blank.
Ideally, setting a CSS variable using calc should be available in two variants:
One variant working just like this example, simply dropping in the string as-is, bar any in-string variable replacements.
A second variant where calc() would yield the result of the calculation instead of simply replacing the string.
How to achieve this? I'd rather leave the actual implementation to people suited to it, but one possibility would be an eval() kind of thing; like eval(calc(100% / 2)) would give the result 250px.
Anyway, if anyone have any real documentation on this behaviour or a solution to how to get the example above to yield the result instead, I'm all ears!
Edit: Just FYI, I have read the specs at https://drafts.csswg.org/css-variables/
This is kind of a tough question to answer cause the answer will not be:
Do it like this...then it will work
The problem you are facing is the normal behavior of CSS. It cascades the styles. If what you are trying to achieve would work it would get real messy after a short amount of time.
I mean how cool is it that you can define a variable like this
#test {
--halfWidth: calc(100% / 2);
}
where var(--halfWidth) should always be calc(100% / 2). Did you note that it will always be half the width of the parent element?
Imagine how strange it would be if a programmer in a few months reads your code and has box with a width of 1000px set with --halfWidth and now it is 250px wide ... I would think the internet is broken :) It should just be 500px wide.
To achieve what you want, you could/should define different vars defining the widths of the parent elements. And split it down to the children.
One approach to this is to dynamically add a line to the CSS Object Model (CSSOM) which explicitly declares the width of the .halfwidth class.
This width will then apply to all divs with the .halfwidth class.
In the example below, I have, additionally, made .element-a horizontally resizable, so that you can see more clearly that as you change the width of .element-a, the width of both .halfwidth divs changes proportionately, including the .halfwidth div which is a child of .element-b.
Working Example:
let myStylesheet = document.styleSheets[0];
const elementA = document.getElementsByClassName('element-a')[0];
let elementAWidth = window.getComputedStyle(elementA).getPropertyValue('width');
const calculateHalfWidth = (elementAWidth) => {
myStylesheet.insertRule('.halfWidth { width: ' + (parseInt(elementAWidth) / 2) + 'px; }', myStylesheet.cssRules.length);
}
calculateHalfWidth(elementAWidth);
// ================================================================================
// THE SCRIPT BELOW USES A ResizeObserver TO WATCH THE RESIZABLE .element-a DIV
// ================================================================================
const elementAObserver = new ResizeObserver(entries => {
for (let entry of entries) {
if (entry.contentRect.width !== elementAWidth) {
calculateHalfWidth(entry.contentRect.width);
}
}
});
elementAObserver.observe(elementA);
body {
font-family: sans-serif;
}
div.element {
float: left;
width: 200px;
height: 100px;
margin: 12px 3px;
text-align: center;
border: 1px solid rgb(0, 0, 0);
}
div.element h2 {
font-size: 18px;
}
div.element-a {
resize: horizontal;
overflow: auto;
}
div.element-b {
width: 300px;
}
div.halfWidth {
height: 40px;
margin: 0 auto;
border: 1px dashed rgb(255, 0, 0);
}
div.halfWidth h2 {
font-size: 14px;
}
<div class="element element-a">
<h2>Element A</h2>
<div class="halfWidth">
<h2>halfWidth</h2>
</div>
</div>
<div class="element element-b">
<h2>Element B</h2>
<div class="halfWidth">
<h2>halfWidth</h2>
</div>
</div>
I'm getting some strange behavior and was wondering if I am using a grid-column-row incorrectly.
I have the following HTML structure:
<div id="homeTop">
<!-- homeTop content such as headings and other nested rows and columns -->
</div>
<div id="homeMain">
<div id="homeMain-left"></div>
<div id="homeMain-right"></div>
</div>
<div id="homeBottom">
<!-- homeBottom content such as headings and other nested rows and columns -->
</div>
I then have the following SCSS to style this content:
#homeTop {
#include grid-column-row;
}
#homeMain {
#include grid-row;
}
#homeMain-left {
#include grid-column(12);
#include breakpoint(medium) {
#include grid-column(8/12);
}
}
#homeMain-right {
#include grid-column(12);
#include breakpoint(medium) {
#include grid-column(4/12);
}
}
#homeBottom {
#include grid-column-row;
}
When I view the page on a desktop-sized screen, there is one line of css that's causing the homeBottom div to float right, which is throwing off the layout. The line of CSS causing the issue is here:
#homeBottom:last-child:not(:first-child) {
float: right;
}
Since a column-row is meant to be a single element acting as a row and a column, in other words, a full width container, I'm confused why I would ever want it to have a float property. It seems that this line of CSS makes sense for columns, but not for column-rows, since the column-row behavior shouldn't depend on whether or not it's the last-child of its parent.
Is this a bug, or am I using the column-row incorrectly? I'm just trying to avoid setting homeBottom as a grid-row, and then including an extra html element inside of it just to act as a full-width grid-column. As you can see, this isn't necessary for homeTop, even though it's also using the grid-column-row mixin. This makes me think I may be using it incorrectly.
I guess another option would be to define my own my-grid-column-row mixin that includes the float declaration:
#mixin my-grid-column-row {
#include grid-column-row;
float: none !important;
}
But this seems like it shouldn't be necessary.
It looks like this is an issue with foundation:
http://github.com/zurb/foundation-sites/issues/8108
My workaround for now is to override the grid-column-row mixin with the following:
#mixin grid-column-row(
$gutter: $grid-column-gutter
) {
#include grid-row;
#include grid-column($gutter: $gutter);
float: none !important;
}
I am attempting to define some default behaviors for a grid and then override them at specific breakpoints. In the following example I would like the two divs to be stacked on top of each other, with slightly modified gutter settings from the default, and then at 800px and above I would like the divs to stack next to each other. The second part does not happen. Seems like some margin settings from the less than 800px scenario are being applied to the greater than 800px scenario. Please let me know how to code this and adhere to susy best practices.
HTML:
<div class="container">
<div class="primary">
<p>I am Primary</p>
</div>
<div class="secondary">
<p>I am Secondary</p>
</div>
</div>
SCSS:
$susy:
(
flow: ltr,
output: float,
math: fluid,
column-width: false,
container: 1200px,
container-position: center,
last-flow: to, columns: 12,
gutters: 1 / 4,
gutter-position: after,
global-box-sizing: border-box,
debug: (
image: hide,
color: rgba(#66f, 0.25),
spot: background, toggle: bottom right)
);
* {
#include box-sizing(border-box);
}
.container{
#include container;
}
.primary{
background-color: red;
}
.secondary{
background-color: blue;
}
// Mobile first layout with slightly different
// gutter settings from default
#include with-layout(12 0.5 split){
.primary{
#include span(12);
}
.secondary{
#include span(12);
}
}
// this layout should take over at 800px and above
// and share a row but instead boxes end up on different
// rows
#include susy-breakpoint(800px, $susy)
{
.primary{
#include span(first 6);
}
.secondary{
#include span(last 6);
}
}
I also made a codepen example that can be found here:
http://codepen.io/sbonham/pen/vLKvMJ
Yep, Susy is just writing CSS values, so you have to handle this the same way you would with plain CSS. Susy doesn't know your DOM, so it has no way of knowing that you want to override values that you set before. If we assumed you always want to override, we would have to output massively bloated code.
There are two solutions here:
Put your small-screen layout inside a max-width media-query, so it doesn't affect larger screens.
Or: override those global values inside the large-screen media-query. The problem to fix is the extra margins added by your initial split gutters.
I prefer the first solution, because I think overrides are ugly. But if you're dealing with some small browsers that doesn't support media-queries (do those still exist?), then you'll need to use the second solution. Try:
#include susy-breakpoint(max-width 800px, 12 0.5 split) {
.primary{
#include span(12);
}
.secondary{
#include span(12);
}
}
this seems like a hack, but hopefully you get something out of this! I added the following to your codepen:
.primary, .secondary {
display: inline-block;
margin: gutter(12);
width: span(12);
width:500px;
}
http://codepen.io/alexG53090/pen/wMWNzR
On their website, the first feature is "Experience cleaner markup without presentational classes.", how do they solve this problem?
I think what Compass has to offer in order to allow us to create cleaner and semantic markup comes for free if you already use SASS alone.
By instance take this trivial example:
Some Mixins
#mixin box {
display: block;
}
#mixin sized_box($width:auto, $height:auto) {
#include box;
width: $width;
height: $height;
}
#mixin floated_box($direction:none, $width:auto, $height:auto) {
#include sized_box($width, $height);
float: $direction;
}
#mixin left_box($width:auto, $height:auto) {
#include floated_box(left, $width, $height);
}
#mixin right_box($width:auto, $height:auto) {
#include floated_box(right, $width, $height);
}
A Placeholder
// divs will be red
div%colored_floating {
#include left_box;
background-color: #ff0000;
}
// paragraphs will be blue
p%colored_floating {
#include right_box;
background-color: #0000ff;
}
Our stylesheet
// if #some.selector * turns out to be a div it will be red,
// and if it is a paragraph it will be blue
#some.selector *{
#extend %colored_floating;
}
Finally on your markup, you don't need any presentational classes
Except for those to make the placeholders more specific, of course.
<section id="some" class="selector">
<div>This will float and it will be red</div>
<p>But this will float right and will be blue</p>
</section>
You could always do:
// to make the placeholders absolutely generic to the whole markup,
* {
#extend %colored_floating;
}
Again, sorry for the very trivial example, but hopefully it will give you an idea on how to get rid of the presentational classes on your markup, aiming to pure semantic content.
What Compass gives us in addition is a complete framework of these mixins, placeholders and so on, ready to be used for good.
Cheers!
Is it possibile to add span3 class in a mixin to avoid putting it in every element in my HTML?
Something like:
.myclass {
.span3;
// other rules...
}
EDIT
I apologize I forgot to specify an important detail: span3 is a standard class of Bootstrap.
I didn't find its definition in any file of the Bootstrap framework.
New Answer (requires LESS 1.4.0)
What you actually desire is something known as extending in LESS and SASS terminology. For example, you want an HTML element (just an example)...
<div class="myclass"></div>
...to fully behave as if it had a span3 class from bootstrap added to it, but without actually adding that class in the HTML. This can be done in LESS 1.4.0 using :extend(), but still not easily, mainly because of the dynamic class generation of bootstrap will not be picked up by :extend().
Here is an example. Assume this initial LESS code (not dynamically generated .span3 classes as bootstrap does):
.span3 {
width: 150px;
}
.someClass .span3 {
font-size: 12px;
}
.someOtherClass.span3 {
background: blue;
}
You add this LESS code in 1.4.0:
.myclass {
&:extend(.span3);
}
Which produces this CSS:
.span3,
.myclass {
width: 150px;
}
.someClass .span3 {
font-size: 12px;
}
.someOtherClass.span3 {
background: blue;
}
NOTE how it did not automatically extend the other instances of .span3. This is different than SASS, but it only means you need to be a bit more explicit in extending. This has the advantage of avoiding excessive CSS code bloat.
To fully extend, simply add the all keyword in the extend() (this is updated from my original code, as I was unaware of the all option):
.myclass {
&:extend(.span3 all);
}
Which produces this:
.span3,
.myclass {
width: 150px;
}
.someClass .span3,
.someClass .myclass {
font-size: 12px;
}
.someOtherClass.span3,
.someOtherClass.myclass {
background: blue;
}
That makes your .myclass fully equivalent (in my example) to the .span3 class. What this means in your case, however, is that you need to redefine any dynamic class generations of bootstrap to be non-dynamic. Something like this:
.span3 {
.span(3);
}
This is so the :extend(.span3) will find a hard coded class to extend to. This would need to be done for any selector string that dynamically uses .span#{index} to add the .span3.
Original Answer
This answer assumed you desired to mixin properties from a dynamically generated class (that is what I thought your issue was).
Okay, I believe I discovered your issue. First of all, bootstrap defines the .spanX series of classes in the mixins.less file, so you obviously need to be sure you are including that in your bootstrap load. However, I assume it is a given that you have those included already.
Main Problem
The main issue is how bootstrap is generating those now, through a dynamic class name in a loop. This is the loop that defines the .spanX series:
.spanX (#index) when (#index > 0) {
.span#{index} { .span(#index); }
.spanX(#index - 1);
}
.spanX (0) {}
Currently, because the class name itself is being dynamically generated, it cannot be used as a mixin name. I don't know if this is a bug or merely a limitation of LESS, but I do know that at present time of writing, any dynamically generated class name does not function as a mixin name. Therefore, .span3 may be in the CSS code to put as a class in your HTML, but it is not directly available to access for mixin purposes.
The Fix
However, because of how they have structured the code, you can still get what you need, because as you can see above in the loop code, they use a true mixin itself to define the code for the .spanX classes. Therefore, you should be able to do this:
.myclass {
.span(3);
// other rules...
}
The .span(3) code is what the loop is using to populate the .span3 class, so calling it for your classes will give the same code that .span3 has. Specifically bootstrap has this defined in mixins.less for that mixin:
.span (#columns) {
width: (#fluidGridColumnWidth * #columns) + (#fluidGridGutterWidth * (#columns - 1));
*width: (#fluidGridColumnWidth * #columns) + (#fluidGridGutterWidth * (#columns - 1)) - (.5 / #gridRowWidth * 100 * 1%);
}
So you will get the width properties for the .span3 in your .myclass.
This is easy to accomplish with Less.js, but the real question is: "Should I mix structural grid classes with my non-structural classes?"
And the answer is no.
This is a bad idea, the advantage of a grid system is that it creates a separation of concerns between structural styling and other styling. I'm not saying "it should never, ever, ever be done". But in general, it shouldn't. I don't even like seeing this:
<div class="span3 sidebar">
<ul class="nav">
...
</ul>
</div>
Where the span3 is in the same div as the .sidebar class. The problem with this is that now your sidebar is not just "floating around" inside a column of the grid, it has become part of the grid - which (in general) makes it even more difficult to maintain your styles because of the workarounds you need to create to force this kind of styling to be responsive.
Well you can do it but you have to define .somethign first, in this example I will do it font-wight: bold; and font-size 20px. As you can see in second class .body I didn't have to define font-weight: bold; and font-size: 20px; I just added .something into it
.something {
font-weight: bold;
font-size: 20px;
}
.body {
background-color: gray;
border: ridge 2px black;
.something
}
You can see example here. http://jsfiddle.net/7GMZd/
YOU CAN'T DO IT THIS WAY
.body {
background-color: gray;
border: ridge 2px black;
.something {
font-weight: bold;
font-size: 20px;
}
}