SASS calculate column widths for grid - css

I have written this piece of code to calculate column widths for my grid system:
#mixin calc-width($index, $type) {
$column-calculation: (100% - ($gutter-width-procent * ($column-count - 1))) / $column-count;
$column-width: ($column-calculation * $index);
$gutter: ($gutter-width-procent * ($index - 1));
#if ($index > 0) {
#if ($type != 'width') {
#{$type}: $column-width + $gutter + $gutter-width-procent;
} #else {
#{$type}: $column-width + $gutter;
}
}
}
I am calling that up in a different function like this:
#for $index from 1 through $column-count {
&.size-#{$index} {
#include calc-width($index, 'width');
}
}
With the variables of:
$column-count: 12 !default; //12
$row-max-width: 1024;
$gutter-width-px: 15px !default; //In px
$gutter-width-procent: percentage($gutter-width-px / $row-max-width);
I like the system. But there is one thing that doesnt seem correct...
Can i do this line on another way, then substracting from 100%?
$column-calculation: (100% - ($gutter-width-procent * ($column-count - 1))) / $column-count;

I may be getting the wrong end of the stick here but if if you used box-sizing: border-box; and padding for your gutters then you wouldn't have to do any calculation. There's a great post about border-box here.

Not really sure, if this solves your problem, but i wouldn't create the grid gutters like that. You can easily create them if you use "box-sizing", a negative margin on the parent and a padding for every column. So your ("pseudo")-HTML could look like:
.parent
.column
.column
.column
And your CSS like that:
.parent {
margin-left: -GUTTER;
}
.child {
padding-left: GUTTER;
}
Your SASS function to create the widths for the columns could then be simplified as well. I made a pen to show you one way to solve your gutter problem.
This SASS function is just for testing and doesn't check for duplicates (.size1-2 (50%) and .size2-4) and other stuff.

Related

Sass Grid System - Implementing gutters

I am trying to implement gutters for the following code(https://www.sassmeister.com/gist/eca78ee1435202c7e7dcaecc57c75bd5):
// ----
// Sass (vundefined)
// Compass (vundefined)
// dart-sass (v1.6.2)
// ----
//Variable Declarations
$__grid--columns:12;
$__grid--breakpoints: (
'xxxsmall': 375px,
'xxsmall': 480px,
'xsmall': 667px,
'small': 768px,
'medium': 960px,
'large': 1024px,
'xlarge': 1200px,
'xxlarge': 1400px,
'xxxlarge': 1600px,
);
$__grid--gutters: (
'small': 30px,
'medium': 30px,
'large': 30px
);
$__grid--cell-containers: (
'small': 1200px,
'medium': 1400px,
'large': 1600px,
'full': 100%
);
//Mixins for Grid
// #mixin createGutters() {
// .element {
// #if map-has-key($__grid--gutters, '') {
// content: 'Key Found';
// } #else {
// content: 'Key Not Found';
// }
// }
// }
#mixin createCells() {
#each $key, $value in $__grid--breakpoints {
#media screen and (min-width:$value){
#for $i from 1 through $__grid--columns {
&.#{$key}-#{$i}{
#if map-has-key($__grid--gutters, $key) {
margin-left:map-get($__grid--gutters, $key);
}
width:((100% / $__grid--columns) * $i);
}
}
}
}
}
//Spit out the cells
.row {
display:flex;
flex-wrap:wrap;
}
.cell {
// #include createGutters;
#include createCells;
}
//Styles not needed for grid
// * {
// box-sizing:border-box;
// }
// .color {
// padding:10px;
// background-color:salmon;
// }
As you can see I have a sass map for the gutters. I am trying to keep this as simple as possible. I'm not sure if I should be using an each loop, or a map-get() function, or maybe something else. I also want to have the margins on the left. I have to consider that if their are too many columns they will drop to the next line.
So basically if I set the margin of the first element to 0, and I have 4 columns that will fit within the container, the 5th item onward on will drop to the next line. The problem is that the margin on the 5th item will still be there.
This is a representation of what I mean:
item---item---item---item
---item---item---item---item
---item---item---item---item
So is there a way to:
Implement my sass map in a succinct way?
Add in support for if the items break to the next line for the margin
Better way to do this process? If so, feel free to fork the sassmeister code.
Your questions contains several points so I will try to answer them in an order that, think, will work better.
Normally purpose of the grid system (or columns system, to name it better to avoid names collision with CSS Grid specification) is to simplify elements positioning by providing ability for elements to take space of one or multiple "columns" defined by the grid. This definition means that columns can't wrap, so your flex-wrap: wrap breaks whole idea of columns system.
Your column width math width:((100% / $__grid--columns) * $i); does not include the fact that grid consists not just of columns, but also of gutters between them. Usually gutters are available only between columns so for 12-columns grid you need to have 11 gutters of defined size. It means that your actual math for grid column width should use calc() expression and it actual math will look like: width: calc(#{100% / $__grid--columns * $i} - #{$gutter-size / $__grid--columns * ($__grid--columns - $i)}); where $gutter-size is current gutter size. I've prepared a CodePen example to demonstrate this math.
If you want your grids to be even better - it is worth to let grid maths to be performed by dedicated library. Try to use Susy 3 for this purpose and your result will became much better.

How to Assign CSS Variable Value to scss Variable or Expression

I'm trying to build my own tiny scalable grid in CSS / scss.
So far I found this decision:
:root {
--page-width: 1170px;
--gutter: 15px;
--columns: 12;
}
.wrapper {
max-width: var(--page-width);
margin-right: auto;
margin-left: auto;
padding-left: var(--gutter);
padding-right: var(--gutter);
}
.row {
margin-left: calc(-1 * var(--gutter));
margin-right: calc(-1 * var(--gutter));
}
.col {
display: block;
margin-left: var(--gutter);
margin-right: var(--gutter);
}
Then I tried to use scss to shorten columns classes description (which at the same time will allow me to change number of columns in one single place in whole code - in CSS Variable --columns) like this
#for $n from 1 through var(--columns) {
.col-#{$n} {width: calc( #{$n} / var(--columns) - var(--gutter) * 2 ); }
}
but it didn't work. The interesting detail is that when I change #for statement from #for $n from 1 throughvar(--columns)`` to #for $n from 1 through12 it compiles well. And there is no problem in compiling CSS-Variable inside #for body. .col-#{$n} {width: calc( #{$n} / var(--columns) - var(--gutter) * 2 ); } compiles well into needed series of classes.
If I use scss variable $columns instead of CSS variable then I'll need to import my grid.scss file into all other scss files of the project.
It's my first question on StackOverflow, so let me know if any other details are needed.
CSS and SCSS variables are two very different things (please see this pen)
To make it work you need a static variable for SCSS to compile
// static (SCSS) variables used produce CSS output
$page-width: 1170px;
$gutter : 15px
$columns: 12;
// dynamic (CSS) variables used at run-time
// note the values are interpolated
:root {
--page-width: #{$page-width};
--gutter : #{$gutter};
--columns: #{$columns};
}
// the for loop is aimed at producing CSS output
// ... why you need the static variable
#for $n from 1 through $columns {
// the content becomes CSS output
// ... why you can use dynamic variables
.col-#{$n} {width: calc( #{$n} / var(--columns) - var(--gutter) * 2 ); }
}
You need to use interpolation (eg. #{$var}) on your variable in order for Sass to treat it as a CSS property. Without it, you're just performing variable assignment.
#mixin w_fluid($property_name, $w_element, $w_parent:16) {
#{$property_name}: percentage(($w_element / $w_parent));
}
The accepted answer is no longer valid. Newer versions of SASS require interpolation to be used for variables.
Refer here for more details
$accent-color: #fbbc04;
:root {
// WRONG, will not work in recent Sass versions.
--accent-color-wrong: $accent-color;
// RIGHT, will work in all Sass versions.
--accent-color-right: #{$accent-color};
}

Using LESS variables in media queries

When I enter the following less code: (paste it into http://less2css.org)
#item-width: 120px;
#num-cols: 3;
#margins: 2 * 20px;
#layout-max-width: #num-cols * #item-width + #margins;
#media (min-width: #layout-max-width) {
.list {
width: #layout-max-width;
}
}
... the resulting CSS is:
#media (min-width: 3 * 120px + 40px) {
.list {
width: 400px;
}
}
Notice that the same variable #layout-max-width - in the media query it produces an expression (which isn't what I want) and when used as the value for the width property it produces 400px (which is also what I want for the media query.)
Is there formal syntax in LESS which enables me to do this?
If not - is there a workaround?
Due to Math Settings
LESS by default expects math operations to be in parenthesis (strict-math=on). So your variable needs those around the values to calculate correctly, like so:
#layout-max-width: (#num-cols * #item-width + #margins);
Then your original code will output as you expect.

How to solve and compensate the sub-pixel rounding issue?

I try to iron out the sub-pixel rounding error for quite a while now, but so far i've failed miserably again and again. I try to accomplish that endeavour with the help of Sass and Susy. The mixin i've used in my last try i got from the Susy issue tracker on Github (i've used space, columns as well as push on the margin-left/right property like suggested there):
#mixin isolate($location, $context: $columns, $dir: 'ltr') {
#if $dir == 'ltr' {
margin-right: -100%;
margin-left: space($location, $context);
}
#else if $dir == 'rtl' {
margin-left: -100%;
margin-right: space($location, $context);
}
}
My Scss looks like the following:
.imggrid{
#include with-grid-settings($gutter: 0.1%){
$y:2;
li{
#include span-columns(2,12);
#for $x from 1 through 30
{
&:nth-child(#{$x}){
#include isolate($y,12);
$y: $y + 2;
#if $y > 12{
$y: 2;
}
}
}
#include nth-omega(6n);
}
}
}
First i've created a custom gutter for the image grid. Then i've defined a variable y to iterate up in the steps of two to be able to call the isolate mixin (isolate(2,12) isolate (4,12) etc). For values larger than 12 the value gets reset to two within the for loop in the end. Then i span a column for each li walking through the 30 images. Each time calling the iterating isolate mixin. After the for loop i've appended the #include nth-omega(6n); to get a new line after each sixth element.
But somehow it doesn't work at all. The first four rows are missing the first image and on the last row the first five elements are missing. Any ideas and suggestions are appreciated. Thanks Ralf
UPDATE: I adjusted these mixins to be 1-indexed rather than 0-indexed. I think that is the more common.
You're close, but the math isn't quite right. It get's a bit complex to keep everything straight, so I wrote up a mixin to take care of it for you. I also adjusted the isolate mixin so that it uses the existing Susy $from-direction variable:
#mixin isolate($location, $context: $total-columns, $from: $from-direction) {
$to: opposite-position($from);
margin-#{$to}: -100%;
margin-#{$from}: space($location - 1, $context);
}
#mixin isolate-list($columns, $context: $total-columns, $from: $from-direction) {
$position: 1;
$line: floor($context / $columns);
#include span-columns($columns, $context, $from: $from);
#for $item from 1 through $line {
$nth: '#{$line}n + #{$item}';
&:nth-child(#{$nth}) {
#include isolate($position, $context, $from);
#if $position == 1 { clear: $from; }
$position: $position + $columns;
#if $position > $context { $position: 1; }
}
}
}
Use it just like span-columns, width & context. That's all there is too it:
.imggrid{
li{
#include isolate-list(4,12);
}
}
That should work with any width, in any context, for any number of list items. Cheers!

Need help creating a percentage-based SASS grid system

My maths is shocking & I cannot, for the life of me get this calculation to work. I am creating a CSS grid system that allows users to specify any number of grid columns (there are 12 by default), the gutter in between these columns, the maximum width around the columns & the margin inside that total width. The hopeful outcome is a percentage width for each column width & a percentage for the left margin of all columns.
If I use the defaults below, the max width will be 1200px, the inner margin on the left & right will be 20 giving an inner width of 1160 for the maximum allowed space for the grid. Does that make sense?
I'm using is SASS by the way. Look at the comments in the code to see what is currently working & not working.
Here is the code on jsFiddle: http://jsfiddle.net/mrmartineau/fMeBk/
$gridColumnCount : 12; // Total column count
$gridGutterWidth : 40; // [in pixels]
$gridColumnPadding : 30; // [in pixels]
$gridMaxWidth : 1200; // [in pixels]
$gridMargin : 20; // [in pixels] Space outside the grid
#function gridColumnWidth() {
#return $gridMaxWidth / $gridColumnCount;
}
// Grid calculations
#function gridColumnWidthCalc($colNumber) {
// Is correct
#if $gridGutterWidth == 0 {
#return percentage($colNumber / $gridColumnCount);
}
// Is incorrect
#else $gridMargin > 0 {
#return percentage( (($colNumber / $gridColumnCount) - gutterCalc(false) ) );
}
}
#mixin columns($columnSpan: 1) {
width: gridColumnWidthCalc($columnSpan);
}
#function gutterCalc($showUnit: true) {
#if $showUnit == true {
#return percentage( $gridGutterWidth / ( $gridMaxWidth - ($gridMargin * 2) ) );
} #else {
#return $gridGutterWidth / ( $gridMaxWidth - ($gridMargin * 2) ) * 100;
}
}
#mixin gridColumn() {
#if $gridGutterWidth > 0 {
margin-left: gutterCalc();
}
#if $gridColumnPadding > 0 {
padding: $gridColumnPadding + px;
}
float: left;
min-height: 30px;
position: relative;
clear: none;
&:first-child {
margin-left: 0;
}
background-color: pink;
border: 1px solid red;
}
#for $i from 1 to $gridColumnCount + 1 {
.span#{$i} { #include columns($i); }
}
.container {
padding-left: $gridMargin + px;
padding-right: $gridMargin + px;
max-width: $gridMaxWidth + px;
}
.col {
#include gridColumn();
}
​
Okay, i spent an hour to comprehend your commentless code.
First of all, there's ambiguity between "columns" as grid units and "columns" as actual elements. Below i'm calling the latter "blocks".
You are correctly overriding gutter of the first block in a row. Thus, the total number of gutters is one less than the number of blocks in a row.
But when you're calculating block widths, you're subtracting gutter from every column without taking into consideration that there are less gutters than blocks.
So you should proportionally reduce the width of a block.
Working solution:
// Accepts a number of columns that a block should span.
// Returns a percentage width for that block.
#mixin columns($columnSpan: 1) {
$number-of-blocks-in-container: $gridColumnCount / $columnSpan;
$total-width-of-all-gutters: gutterCalc(false) * ($number-of-blocks-in-container - 1);
$total-width-of-all-blocks: 1 - $total-width-of-all-gutters;
$width-of-a-single-block: $total-width-of-all-blocks / $number-of-blocks-in-container;
width: percentage( $width-of-a-single-block );
}
Now your wheel is rolling! See it in action: http://jsfiddle.net/fMeBk/46/ Keep in mind that browsers round up percentage values with a slight error, so grid positioning might be not pixel-perfect. BTW, right-aligning the last block in a row is necessary to minimize the visual effect of this rounding error.
Dude, your code architecture is so wrong, and your approach has a number of drawbacks. I can name them if you want.
You really should give Susy another try. It is a brilliant piece of SASS and also a great source to learn SASS techniques from. Its source code is well commented and available on GitHub.
According to you, what features of your grid framework does Susy lack?
I assure you, Susy can do what you want. Just explain a task and i'll try to come up with an elegant solution leveraging Susy.
PS I'm not trying to dissuade you from experimenting. Practice makes perfect! Experimenting is necessary, and you're doing a great job. What i'm trying to tell is that you should follow a good example and adopt good practices so that you don't end up in the wrong place.
PPS Please give me back my rating. I devoted a lot of my personal time trying to help you, and you minused my answer. :(
You didn't state any specific question, and that's against StackOverflow rules.
And without your explainations of the structure of your framework, it's hard to understand what exactly you're trying to achieve with each function and mixin.
How are we supposed to help?
Anyway, you're approach is faulty for two reasons:
You're trying to re-invent the wheel. There are dozens of grid frameworks already.
You're using a non-semantic approach. You're styling by applying style-specific classes in HTML. Instead, your classes should be semantic (i. e. state the function of blocks, not their look) and styling should be applied to those classes in CSS. With SASS, it's very easy.
Solution: use Susy. It's a fantastic piece of software, and its author Eric Meyer is very responsive here on StackOverflow.
With Susy, your code could look like this:
HTML:
<div id="container">
<div id="gallery">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div id="promos">
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div id="footer">
<div id="bottom-menu"></div>
<div id="partners"></div>
<div id="social-buttons"></div>
</div>
<div>
<div class="col span12"></div>
</div>
</div>
SASS:
//////////
// Imports
//////////
#import susy
//////////
// Defining variables
//////////
// Main grid properties
$total-columns : 12 // Number of columns
$container-style: fluid // The grid should stretch
$max-width : 1200px // But not wider than this
// Proportions between column width and gutter between columns
$column-width : 85%
$gutter-width : 100% - $column-width
// Setting margins around the grid
$grid-padding : 1em // This will remain absolute
$container-width: 100% // If you want relative margins, change this instead
//////////
// Applying containers and columns
//////////
// Setting containers in all immediate children of #container
#container > *
+container /* ← Actual Susy magic! :D */
max-width: $max-width
// Setting columns
#gallery > *
+span-columns(1)
&:last-child // The last column should be marked with "omega"
+span-columns(1 omega) // to compensate browsers' calculation errors
#promos > *
+span-columns(2)
&:last-child
+span-columns(2 omega)
// #footer
#bottom-menu
+span-columns(6)
#partners
+span-columns(4)
#social-buttons
+span-columns(2 omega)
Sorry, i haven't tested this code, it may contain errors, but you can still see the idea.
Susy also lets you easily create responsive grids. And they say Susy is gonna be the default grid engine in Compass.
UPD: See the question-specific answer next to this one!
I put together a simple SASS percentage based grid generator a little while back. The math that you're looking for is here:
https://github.com/jordancooperman/simple_grid/blob/master/assets/css/scss/partials/_grid.scss
Some of the css in there is for display purposes only so that you will see your grid if using the markup that's also included in the project. Let me know if you have any questions, cheers!

Resources