Is there any (good) way to extend a class within a mixin, and then use that mixin within a media query, using Less? - less

I've been working on building out some Less files to help speed up my CSS workflow, and also to help produce more efficient, cleaner CSS.
The way I see it:
Mixins are a great way to help speed up the workflow, but they have the drawback of potentially making the outputted CSS longer than necessary.
Extending classes is the ideal solution for ensuring the amount of duplicate style declarations is minimized, helping clean that up...
So, to help balance things out I wrote out a set of standard, commonly used styles, using dummy classes (they are stored in a file which is imported by reference, so the styles are only output if they get extended).
I set all of my Mixins to extend these classes wherever possible, which worked great for the most part.
However, I realized my pitfall once I got to my media queries... I can't extend those classes within the media query, which would be fine normally, I would just remember not to do so.. But since the Mixins also now use my extends, I can now no longer use them inside media queries either.
I'm not willing to avoid using the Mixins inside of the media queries because of this, but I'd really love to be able to find a way to keep extending classes within them to keep my output as clean as possible.
The only idea I've thought of so far is to add an extra parameter to every Mixin to specify wether it should extend or not, but that's less than ideal.
My hope is that someone can come up with a much more clever solution, that would allow me to maintain the benefit of Mixins which extend base style classes, but also maintain easy usability, without over complicating things. Might be a tall order, but here's hoping.
In case my explanation was hard to follow, this is what I would have hoped to be able to do, but is not currently possible:
Ideal Input
// extensions.less
.block {
display: block;
}
// mixins.less
#import (reference) "extensions";
.mixin {
&:extend(.block);
margin: auto;
}
// styles.less
#import "mixins";
.element1 {
.mixin();
}
.element2 {
.mixin();
}
#media only screen and (max-width: 768px) {
.element3 {
.mixin();
}
.element4 {
.mixin();
}
}
Ideal Output
// styles.css
.element1, .element2 {
display: block;
}
.element1 {
margin: auto;
}
.element2 {
margin: auto;
}
#media only screen and (max-width: 768px) {
.element3, .element4 {
display: block;
}
.element3 {
margin: auto;
}
.element4 {
margin: auto;
}
}

In short, yes, currently it is somewhat possible but requires some additional wrapping for a top level classes:
// extensions.less
.block {
display: block;
}
// mixins.less
#import (reference) "extensions";
.mixin() {
&:extend(.block);
margin: auto;
}
// styles.less
#media all { // !
#import "mixins";
.element1 {
.mixin();
}
.element2 {
.mixin();
}
}
#media only screen and (max-width: 768px) {
#import (multiple) "mixins";
.element3 {
.mixin();
}
.element4 {
.mixin();
}
}
.element1 and .element2 (and any other class to extend .block) have to be put into #media all because currently:
Top level extend matches everything including selectors inside nested media
So if .element1 and .element2 stay in the global scope they leak into every other #media .block declaration.
(Hmm, actually for me this "top level extend matches everything" thing looks questionable and contradicts another "extend inside a media declaration should match only selectors inside the same media declaration" rule (obviously because global scope = #media all thus they should work identically)).

Related

How to use the default variable for only one of a few variables in less

Here is my mixin
.test(#color:black; #width:100px; #height:50px) {
width:#width;
height:#height;
background:#color;
}
Here is where it's called later
.mydiv {.test('use-mixin-color'; 300px; 150px);}
How can I override the size of .mydiv, while using the color defined in the mixin?
Everything I have tried overrides the mixin color.
To Use mixin in LESS, pass those parameter to override mixin default value :
Soluations :
.test(#color:black; #width:100px; #height:50px) {
width : #width;
height : #height;
background : #color;
}
.mydiv {
.test(#width : 300px; #height : 150px);
}
OUTPUT :
.mydiv {
width: 300px;
height: 150px;
background: black;
}
Helpful :)
In addition to the accepted answer. There're multiple methods (actually infinite) but if you want your mixin to be most easy for use you can provide a "specialization" for a specific argument value or number of arguments. Like this for example:
// usage:
.foo {.test(red, 1px, 2px)}
.bar {.test(3px, 4px)}
// impl.:
.test(#color, #width, #height) {
width: #width;
height: #height;
background: #color;
}
.test(#width, #height) { // <- "no color" specialization
.test(black, #width, #height);
}
Demo.
Also think twice before adding default parameter values for a mixin like:
.test(#color: black, #width: 100px, #height: 50px) { ...
People tend to overuse this feature while it's rarely really necessary (and only creates an extra code-noise) except some specific use-cases.
I.e. consider if you actually expect your mixin to be invoked as:
test;
test(blue, 4em);
// etc.
Do you?
It's usually a good idea to start without default parameter values (at least to protect the mixin against accidental misuse), i.e.:
.test(#color, #width, #height) { ...
and add them later only where and when they are necessary.

Inconsistent Output between LESS Compilers

I have written some LESS code that resizes text based on browser width. Multiple different elements and their parameters can be sent to the reusable mixin.
All online LESS compilers output the desired result. But I am getting different output from Squarespace's LESS compiler.
Squarespace's compiler appears to "hang on" to the old variable values when called a second time.
Can you see how Squarespace's LESS compiler is reaching its output and, if so, share changes that can be made to make the output consistent with all other compilers?
Output from online compilers: (desired)
#media screen and (max-width: 1280px) {
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 120px;
}
}
#media screen and (max-width: 640px) {
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 60px;
}
}
#media screen and (max-width: 1280px) {
#divisionTitle {
font-size: 85px;
}
}
#media screen and (max-width: 853.3333333333334px) {
#divisionTitle {
font-size: 56.666666666666664px;
}
}
#media screen and (max-width: 426.6666666666667px) {
#divisionTitle {
font-size: 28.333333333333332px;
}
}
Output from Squarespace compiler: (undesirable)
#media screen and (max-width: 1280px) {
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 120px;
}
}
#media screen and (max-width: 640px) {
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 60px;
}
}
#media screen and (max-width: 1920px) { //<---Gone wrong! Continuing to use element1!
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 180px;
}
}
#media screen and (max-width: 1280px) {
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 120px;
}
}
#media screen and (max-width: 640px) {
.homesCommunitiesLayout #pageHeroWrapper {
font-size: 60px;
}
}
LESS Source Code and Link to code on Less2Css.org:
#maxSiteWidth: 1280px;
#fullWidth: #maxSiteWidth;
//Element 1 Parameters & Function Call
#fitTextElement1: ~".homesCommunitiesLayout #pageHeroWrapper";
#fitTextElement1Max: 120px;
#fitTextElement1Min: 50px;
#fitTextElement1BreakPoints: 2;
.fitText(#fitTextElement1; #fitTextElement1Max; #fitTextElement1Min; #fitTextElement1BreakPoints);
//Element 2 Parameters & Function Call
#fitTextElement2: ~"#divisionTitle";
#fitTextElement2Max: 85px;
#fitTextElement2Min: 26px;
#fitTextElement2BreakPoints: 3;
.fitText(#fitTextElement2; #fitTextElement2Max; #fitTextElement2Min; #fitTextElement2BreakPoints);
//Primary Looping Mixin
.fitText(#targetElement; #targetElementMaxSize; #targetElementMinSize; #targetElementBreakPoints) {
.mixin-loop (#loopIteration) when (#loopIteration > 0) {
#{targetElement} {
.setBreakPointWidth(#loopIteration; #targetElementBreakPoints);
#media screen and (max-width: #breakPointWidth) {
.setFontSize(#loopIteration; #targetElementMaxSize; #targetElementMinSize; #targetElementBreakPoints);
font-size: #targetElementFontSize;
}
}
.mixin-loop(#loopIteration - 1);
}
.mixin-loop(0){}
.mixin-loop(#targetElementBreakPoints);
}
//Function to set font size
.setFontSize(#loopNumber; #maxSize; #minSize; #breakPoints) {
#targetElementFontSize: (#maxSize/#breakPoints)*#loopNumber;
.resetFontSize(#targetElementFontSize; #minSize);
}
//Function to reset font size if below minimum desired
.resetFontSize(#calculatedSize; #minSize) when (#calculatedSize < #minSize) {
#targetElementFontSize: #minSize;
}
//Function to set break point
.setBreakPointWidth(#loopNumber; #breakPoints) {
#breakPointWidth: (#fullWidth/#breakPoints)*#loopNumber;
}
Note that Squarespace uses LESS 1.3.3 so you'll need to manually switch Less2Css to that version (though it doesn't seem to change anything if you don't).
Having put much more time into this, I've discovered there are a lot of issues with the code as I posted it. In older versions of LESS, variables would "leak" up to parent scopes, which is the only reason any of this code was working at all.
In the end, the solution was to abandon the old 1.3.3 version and write for the latest version, rewriting the entire code NOT to depend on such "leaks". Then to precompile using an online compiler until Squarespace updates their compiler someday. For now, I just have to precompile it before saving it to the file that is on the Squarespace Server.
Without getting in the specifics of exactly what went wrong, I'll just mention that the top reason I've had issues with LESS and Squarespace's compiler is because it's not the same as LESS. Squarespace previously used a Node implementation of Less.js, and then rebuilt the compiler in Java to gain performance over Node/Less.js.
So the key takeaway is that Squarespace's LESS compiler is based off of Less.js and not identical to the same LESS compilers a developer would use. You'll definitely find odd scenarios where things won't compile the same.
I would submit any bugs you find to the official repo here: https://github.com/Squarespace/less-compiler. Their engineers are pretty responsive!

Less, multiple imports

I thought within Less you could do imports at the rule level?
e.g. given two Less files with identical variable names but different values
#import (reference) 'file1.less'
.myrule1
{
#import (reference) 'file2.less'
// use varA from file2
}
.myrule2
{
// use varA from file1
}
Is this not allowed, it doesn't seem to be in the latest Less version
Failing that can you do this
#import (reference) 'file2.less'
.myrule1
{
// use varA from file2
}
#import (reference) 'file1.less'
.myrule2
{
// use varA from file1
}
#import (reference) 'file2.less'
.myrule3
{
// use varA from file2 again
}
What am I trying to accomplish here? Kendo UI has multiple themes with colours for grids, headers, etc. Within my less file I want to make something like this
.BlackBasedThemes
{
#import one of the black themes
.MyDiv
{
background-color: #tooltipBackgroundColor;
}
// whole bunch of other stuff
}
.NonBlackBasedThemes
{
#import one of the not black themes
.MyDiv
{
background-color: #tooltipBackgroundColor;
}
// whole bunch of other stuff
}
And then within my code the body gets the NonBlackBasedThemes or NonBlackBasedThemes class. I can just add a MyDiv, etc class to a div and get the theme appropriate colour.
I thought within Less you could do imports at the rule level?
e.g. given two Less files with identical variable names but different values
When using lessc 2.4.0 (Less Compiler) [JavaScript] i can do:
black.less:
#tooltipBackgroundColor: black;
white.less:
#tooltipBackgroundColor: white;
Then the following code:
.BlackBasedThemes
{
#import "black";
.MyDiv
{
background-color: #tooltipBackgroundColor;
}
// whole bunch of other stuff
}
.NonBlackBasedThemes
{
#import "white";
.MyDiv
{
background-color: #tooltipBackgroundColor;
}
// whole bunch of other stuff
}
compiles into:
.BlackBasedThemes .MyDiv {
background-color: black;
}
.NonBlackBasedThemes .MyDiv {
background-color: white;
}
Indeed you do not need the reference keyword (but it should also work when using it). It is not easy to see what your problem is.
Notice that you can also import one of the files into the global scope:
#import "black"; // sets `#tooltipBackgroundColor` for the global scope
.BlackBasedThemes
{
.MyDiv
{
background-color: #tooltipBackgroundColor; // uses `#tooltipBackgroundColor` from the global scope
}
// whole bunch of other stuff
}
.NonBlackBasedThemes
{
#import "white";// sets `#tooltipBackgroundColor` for only the local scope
.MyDiv
{
background-color: #tooltipBackgroundColor;// uses `#tooltipBackgroundColor` from the local scope
}
// whole bunch of other stuff
}

LESS condition based on CSS class to set a LESS variable

I need to set a Less variable to match the website's active theme, ie, each theme has a different color.
I'd like to set #themeColor to the right color, based on the HTML's body CSS class that defines the theme.
For example:
body.themeBlue { #themeColor: blue; }
body.themeRed { #themeColor: red; }
This way I'd only need to use the #themeColor variable inside the other Less files.
Can anyone help?
According to this (http://www.lesscss.org/#-scope) it is possible to do something like that, but I can't make it work. what is going on here?
The LESS file cannot read the actual class applied to the html body element at run time (you would probably need to implement a javascript solution to do something like that).
If you just want to have all themed css ready for use based on the body class, the best way to implement this to have all the necessary theme based css in a mixin, then apply it under the theme classes. This reduces code duplication. For example:
LESS
//mixin with all css that depends on your color
.mainThemeDependentCss() {
#contrast: lighten(#themeColor, 20%);
h1 {color: #themeColor;}
p {background-color: #contrast;}
}
//use the mixin in the themes
body.themeBlue {
#themeColor: blue;
.mainThemeDependentCss();
}
body.themeRed {
#themeColor: red;
.mainThemeDependentCss();
}
CSS Output
body.themeBlue h1 {
color: #0000ff;
}
body.themeBlue p {
background-color: #6666ff;
}
body.themeRed h1 {
color: #ff0000;
}
body.themeRed p {
background-color: #ff6666;
}
For some other answers that deal with aspects or ways of theming, see:
LESS CSS - Change variable value for theme colors depending on body class
LESS.css variable depending on class
LESS CSS: abusing the & Operator when nesting?
Variables in Less are actually constants and will only be defined once.
Scope works within its code braces, so you would need to nest your CSS within each theme you want (which means duplication).
This is not ideal as you would need to do this:
body.themeBlue {
#color: blue;
/* your css here */
}
body.themeRed {
#color: red;
/* your css here AGAIN :( */
}
You could, however, try to use variables like this:
#color: black;
#colorRed: red;
#colorBlue: blue;
h1 {
color: #color; // black
body.themeRed & {
color: #colorRed; // red
}
body.themeBlue & {
color: #colorBlue; // blue
}
}
You would only need to declare the colours once, but you would need to constantly do the "body.themeRed" etc. prefixes where the colour varies depending on the theme.
You could actually use #import to load your theme! So common.less would contain all your default styles and #themeColor will be applied to it.
.mainThemeDependentCss() {
//file with all themed styles
#import 'common.less';
}
//use the mixin in the themes
body.themeBlue {
#themeColor: blue;
.mainThemeDependentCss();
}
body.themeRed {
#themeColor: red;
.mainThemeDependentCss();
}
BTW you should avoid using body selector in your common.less, because it wouldn't work.

Reusing LESS nested styles

Let's say that I have a style defined using Less:
ul.unstyled,
ol.unstyled {
margin-left: 0;
list-style: none;
}
Later on, I want to re-use the unstyled class:
.my-list {
.unstyled;
}
This doesn't work, however, and I can't figure out the magic to make it work. Any thoughts?
You can't re-use arbitrary class definitions, only mixins (those starting with a dot). In this case you'll need to duplicate it.
Try this:
.unstyled {
margin-left: 0;
list-style: none;
}
.my-list {
.unstyled;
}
You won't be able to nest .unstyled if it's defined as ul.unstled and ol.unstyled.
Since you can't reuse .unstyled when it's a nested style and you probably don't want to edit the Bootstrap source code, I'd suggest you just assign both classnames to your list:
<ul class="unstyled my-list" />