15

I have looked around and I've seen one solution where in your html, you'd have a tag dedicated to pass sass variables to javascript. I'm talking about the second answer from

Is there a way to import variables from javascript to sass or vice versa?

I also tried using html

<div class="selector"></div>

with css

.selector { content: "stuff"; }

but looking at the dom in the developer tools, it doesn't get added even though we can see it on the rendered page, so I cannot pick it up with javascript

$('.selector').text()

How does everyone do it?

Community
  • 1
  • 1
BringMeAnother
  • 607
  • 2
  • 10
  • 18
  • You can't pass variables from SASS to JS, the answer you pointed only translated it to JQuery. Anyway, JS is an industry standard, you can always use it combined with CSS ;) – gespinha Aug 27 '13 at 04:25
  • 5
    Perhaps SASS is not meant to be passed to JavaScript, but I can think of at least one place where it would make sense. Suppose I'm making a responsive layout for desktop and mobile. I set my break point at 768px of width in SASS. In JS, it would be useful to know what that breakpoint is instead of declaring a new variable maintain both variables separately. – BringMeAnother Aug 27 '13 at 04:38
  • @Jack "meant to"? The history of web development is one of clever hacks that solve real world problems. How do you think those [cow paths](http://www.w3.org/TR/html-design-principles/#pave-the-cowpaths) got made in the first place? – steveax Aug 27 '13 at 06:28
  • @steveax Clever hacks don't sound like an industry standard to me. – Ja͢ck Aug 27 '13 at 06:37
  • Personally I would see myself define the variables in PHP and pass them to SASS and JS separately. – Ja͢ck Aug 27 '13 at 06:38
  • @Jack well, they aren't until they are. How about all the clever CSS parser hacks to target specific browsers (*html anyone)? While we have better ways today, those were certainly industry standard BitD and solved a lot of real world problems. – steveax Aug 27 '13 at 06:45
  • @steveax CSS hacks don't really count because they're expressed in the same language; this particular problem highlights the inherent disconnect between SASS and JS which imo can only be overcome by somehow making the SASS parser export its variables into a ` – Ja͢ck Aug 27 '13 at 06:55
  • @BringMeAnother - Another situation is for color variables where you pretty much need the color to be places in JAVASCRIPT, for example, when passing a set of colors to some `D3` chart – vsync Feb 22 '17 at 14:24

4 Answers4

10

Not sure about "industry standard", but it's a very handy technique and not too difficult. The content of pseudo elements is not available via text() though, you have to use getComputedStyle.

Example using body:after:

Sass (using the compass breakpoint extension):

body:after {
  display: none;

  @include breakpoint($bp-wide) {
    content: "wide";
  }

  @include breakpoint($bp-medium) {
    content: "medium";
  }

  @include breakpoint($bp-small) {
    content: "small";
  }
}

JavaScript:

if (window.getComputedStyle) {
  var mq = window.getComputedStyle(document.body,':after').getPropertyValue('content');
}

if (mq.indexOf('small') !== -1) {
  // do something
}

Credit: I first saw this technique here: https://coderwall.com/p/_ldtkg

steveax
  • 17,527
  • 6
  • 44
  • 59
  • `getComputedStyle` causes style recalc / and forces layout, which are very bad for performance, it's best to avoid it if possible. – Ray Shan Jan 07 '16 at 23:51
  • @RayShan - still good if you are using it before the page is even rendered, then it means nothing if there's a repaint. anyway, it's all about timing where you place this code which reads the CSS at. – vsync Feb 22 '17 at 14:20
  • @vsync how does one run `getComputedStyle` before page is rendered? If you're running `getComputedStyle` in the browser at any point, it's a performance hit. If view is data driven, data should be in a separate JSON file. SASS is compiled ahead of render anyway so one can add something like https://github.com/Updater/node-sass-json-importer. – Ray Shan Feb 24 '17 at 00:22
  • @RayShan - in SPA pages, where you can run any javascript you want prior to rendering **anything**, so you basically have a blank page, so repaint isn't an issue. it isn't an issue anyway unless it happens in a fast interval (like when doing animations wrongly) – vsync Feb 24 '17 at 16:01
4

I believe that injecting SASS variables via CSS content property is a very hackish way to do things.

Instead, you can store the variables in a separate location and have them read both by SASS and JS.

First, store a list of breakpoints in a breakpoints.json file:

["0", "300px", "500px", "700px", "900px", "1100px"]

Then use Ruby to read this JSON file and make its contents available as a SASS list via a SASS function. Put this into your Compass config.rb:

sass_options = { :custom => {'breakpoint_file' => 'breakpoints.json'} }

# This creates a SASS function debug() that returns $debug into SASS
module Sass::Script::Functions
  def breakpoints

    # Reading an array of breakpoints into a file
    unless breakpoints_array_raw = JSON.load( IO.read( options[:custom]['breakpoint_file'] ))
      raise Sass::SyntaxError.new("Error: Breakpoints file '#{options[:custom]['breakpoint_file']}' does not exist.")
    end

    # Converting strings in the array to SASS String literals
    breakpoints_array_sassy = breakpoints_array_raw.map { |s| Sass::Script::String.new(s) }

    # Returning the list into SASS
    Sass::Script::List.new( breakpoints_array_sassy, :space )
  end
end

In your SASS code, read breakpoints like this:

$breakpoints: breakpoints()

In JS, use jQuery's .get method to request the JSON file like this:

var
  breakpoints = [],
  requestBreakpoints = $.get('breakpoints.json');

requestBreakpoints.done(function (response, textStatus, jqXHR){
    breakpoints = response; // You might want to remove "px" here
});

When i was assembling this setup, I found an existing solution here, but i decided to reimplement it using my favorite SASS tools: Singularity and Breakpoint Slicer.

For your convenience, i've built a proof-of-concept GitHub project with everything set up nicely, featuring some ugly JS code. :)

And here's a live demo!

Andrey Mikhaylov - lolmaus
  • 23,107
  • 6
  • 84
  • 133
  • This is pretty bad performance-wise, because the browser will have to request the extra JSON file, and waiting for that will hold up other js that depends on those variables. – Ethan Apr 11 '14 at 20:11
  • You're right. But if you put the requester in a separate small JS file and include it early, then the JSON file will be being downloaded at the same time as CSS and the JS breakpoints will be applied not too much later after CSS media queries are applied. – Andrey Mikhaylov - lolmaus Apr 13 '14 at 13:31
  • "Then use Ruby". like I even use Ruby in the first place. this is totally unrelated to the problem and should not have been included because that may only work for a minor subset of devs who do use Ruby. – vsync Feb 22 '17 at 14:22
  • @vsync This answer was written 3.5 years ago. At that time, the only available (and full-featured) Sass implementation was written in Ruby. Currently, the most popular Sass implementation is `libsass` written in C and commonly used as an npm package (wrapped into JS). C is faster to run but way slower to develop, so next official implementation is gonna be written in Dart. In a few years, another script kiddie will be telling you: "Wat? npm install? You're so irrelevant, old man!" – Andrey Mikhaylov - lolmaus Feb 22 '17 at 18:19
2

Sharing state trough CSS content property seems dirty. I don't like the idea of making extra XHR requests either.

I've thought of building a custom solution that would just compile SASS file and JS module. But it turned out there's an npm package called rosetta. It does exactly that. Formats of output are pretty flexible and it didn't take me long to set it up as a Grunt.js task.

$ npm install rosetta

Here's example Gruntfile.js:

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
      watch: {
          css: {
              files: ['sass/*.scss', 'sass/lib/*.scss', 'rosetta/*.rose'],
              tasks: ['rosetta', 'compass']
          }
      },
      compass: {
          dist: {
              options: {
                  sassDir: 'sass',
                  cssDir: '../static/css',
                  imagesDir: '../static/img',
                  javascriptsDir: '../static/js'
              }
          }
      },
      rosetta: {
          default: {
             src: ['rosetta/*.rose'],
             options: {
                 jsFormat: 'requirejs',
                 cssFormat: 'scss',
                 jsOut: '../static/js/lib/rosetta.js',
                 cssOut: 'sass/_shared_variables.scss'
             }
          }
      }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-compass');
  grunt.loadNpmTasks('rosetta');
  grunt.registerTask('default', ['watch']);

};

And package.json:

{
  "name": "",
  "version": "0.0.0",
  "description": "",
  "main": "Gruntfile.js",
  "dependencies": {
    "grunt": "^0.4.5",
    "grunt-cli": "^0.1.13",
    "grunt-contrib-compass": "^0.9.1",
    "grunt-contrib-watch": "^0.6.1",
    "rosetta": "git+https://github.com/ukolka/rosetta.git"
  },
  "devDependencies": {},
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

After all that you just need to import _shared_variables.scss in SASS and use rosetta.js module in your JavaScript to access common variables. As you make changes to the *.rose files your styles and JS will be updated.

uKolka
  • 36,422
  • 4
  • 33
  • 44
1

If you are using GULP or GRUNT (I hope you are using the first one)

This example shows how to do it using gulp:

Define the colors in the gulpfile, or in another file and then import it to your gulpfile, and then you can dynamically build both the SCSS variables and the javascript like so:

variables_template.scss (not to be imported in your main scss file)

...
$colors : ([COLORS])
...

app.js (some js file which holds the colors variable)

var appColors = {[COLORS]};

gulpfile.js

var gulp    = require('gulp'),
    gutil   = require('gulp-util'),
    replace = require('gulp-replace');

var appColors = {
   "red"   : "#d63737",
   "green" : "#69c962"
};

gulp.task('scssVars', ()=>{
    var colorsString = JSON.stringify(appColors);
    colorsString  = colorsString.slice(2,colorsString.length - 2);

    return gulp.src('css/variables_template.scss')
        .pipe(replace(/[COLORS]/g, colorsString ))
        .pipe(rename('variables.scss'))
        .pipe(gulp.dest('/'))
});

// do more of less the same for your app.js, only without renaming, if possible

Similar NPM gulp packages for SCSS variables:

vsync
  • 118,978
  • 58
  • 307
  • 400