Giter VIP home page Giter VIP logo

node-sprite's Introduction

Build Status

A node.js Sprite Library with Stylus and Retina Support

Requirements

node-sprite uses ImageMagick for its graphics operations. So make sure you have the convert and identify command available in your envirnoment.

Usage

There are three exported functions: sprite, sprites and stylus. The following examples show how to use them.

Example Directory Stucture

- app.js
- images/
  - global/
    - bar.jpg     // 200x100px image
    - foo.png     // 10x50px   image
  - animals/
    - cat.gif     // 64x64px   image
    - duck.png    // 64x64px   image
    - mouse.gif   // 64x64px   image

Single Sprite

var sprite = require('node-sprite');

sprite.sprite('global', {path: './images'}, function(err, globalSprite) {
  console.log(globalSprite.filename())
  console.log('foo', globalSprite.image('foo'));
  console.log('bar', globalSprite.image('bar'));
});

This code will generate a sprite image named ./images/global-[checksum].png and output the following:

global-45c81.png
foo, {width: 200, height: 100, positionX: 0, positionY: 52}
bar, {width: 64, height: 64, positionX: 0, positionY: 0}

Multiple Sprites

var sprite = require('node-sprite');

sprite.sprites({path: './images'}, function(err, result) {
  var globalSprite = result['global'];
  var animalsSprite = result['animals'];
  console.log(globalSprite.filename());
  console.log(animalsSprite.filename());
  console.log('animals/duck', animalsSprite.image('duck'));
});

This code will generate a sprite image for every subfolder of ./images. The images are named ./images/[folder]-[checksum].png.

global-45c81.png
animals-b775d.png
animals/duck, {width: 10, height: 50, positionX: 0, positionY: 66}

Stylus Integration

// screen.styl
#duck
  sprite animal duck
#mouse
  sprite global mouse false

The sprite function generates the correct background image and position for the specified image. By default it also adds width and height properties. You can prevent this behaviour by setting the third optional parameter to false.

/* screen.css */
#duck {
  background: url('./images/animals-b775d.png') 0px -66px;
  width: 64px;
  height: 64px;
}
#mouse {
  background: url('./images/animals-b775d.png') 0px -132px;
}

The sprite.stylus function behaves similar to sprite.sprites, but it returns a helper object, with provides a stylus helper function helper.fn.

var sprite = require('node-sprite');
var stylus = require('stylus');

var str = require("fs").readFileSync("screen.styl")

sprite.stylus({path: './images'}, function (err, helper) {
  stylus(str)
    .set('filename', 'screen.styl')
    .define('sprite', helper.fn)
    .render(function (err, css) {
      console.log(css);
    });
});

Retina / High Resolution Sprite Support

node-sprite has a special mode for high resolution sprites. When your sprite folder ends with @2x it will be treated differently.

Basic Example

animals@2x/
- cat.gif    // 128x128px image
- duck.png   // 128x128px image

Although we have 128x128px images. The elements should only have the size of 64x64px and the background has to be scaled down.

// screen.styl
#duck
  sprite(animal@2x, duck)
  background-size sprite-dimensions(animal@2x, name)

will be transformed to

/* screen.css */
#duck {
  background: url('./images/[email protected]') 0px -66px;
  width: 64px;
  height: 64px;
  background-size: 64px 194px;
}

For this to work you have to add the sprite-dimensions helper in you stylus configuration:

.define('sprite-dimensions', helper.dimensionsFn)

Retina Mixin

If you want to have a retina and a non-retina sprite it makes sense to create a mixin like this one:

// screen.styl
retina-sprite(folder, name)
  sprite(folder, name)
  hidpi = s("(min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx)");
  @media hidpi
    sprite(folder+"@2x", name, false)
    background-size sprite-dimensions(folder+"@2x", name)

#duck
  retina-sprite animals duck

This will generate the following css code:

#duck {
  background: url('./images/animals-b775d.png') 0px -66px;
  width: 64px;
  height: 64px;
}
@media (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 1.5dppx) {
  #duck {
    background: url('./images/[email protected]') 0px -66px;
    background-size: 64px 194px;
  }
}

Note: All images in the retina folder should have even height and width pixels.

Options

All three functions accept an optional options parameter.

{
  path: './images',     // sprite directory
  padding: 2,           // pixels between images
  httpPath: './images', // used be the stylus helper
  watch: false,         // auto update sprite in background
  retina: '@2x'         // postfix for retina sprite folders
}

Auto Update on Image Change

If you pass watch: true as an option node-sprite will watch the sprite folders and regenerate the sprite when something changes.

You can subscribe to the update event of the sprite or helper object to get notified.

var generateCss = function () {...};

sprite.stylus({watch: true}, function (err, helper) {
  generateCss();
  helper.on("update", generateCss);
});

Structural Sprite Information / JSON

node-sprite will put a ./images/[folder].json next to every generated sprite image. This file contains structural information of the generated sprite. This files can be used by other modules or applications.

They are also usefull if you running your application on a production machine without ImageMagick. In this case node-sprite will fallback to this data.

{
  "name": "animals",
  "checksum": "b775d6fa89ad809d7700c32b491c50f0",
  "shortsum": "b775d",
  "images": [
    {
      "name": "cat",
      "filename": "cat.gif",
      "checksum": "25ce6895f8ed03aa127123430997bbdf",
      "width": 64,
      "height": 64,
      "positionX": 0,
      "positionY": 0
    },
    ...
  ]
}

Contribute

Feel free to post issues or pull request.

You can run the projects tests with the npm test command.

License

The MIT License

node-sprite's People

Contributors

naltatis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

node-sprite's Issues

Sprite._cleanup deletes other -2x spritemaps

If you have two spritemaps being generated by a call to sprites, and they both have 2x versions, the 2x spritemaps will all get deleted.

In sprite.coffee, _cleanup:

  _cleanup: (cb = ->) ->
    self = @
    fs.readdir "#{@path}", (err, files) ->
      for file in files
        if file.match("^#{self.name}-.*\.png$") and file isnt self.filename()
          fs.unlinkSync "#{self.path}/#{file}"
      cb()

The pattern ^#{self.name}-.*\.png will match any -2x files that don't have the correct suffix. If you have two spritemaps being generated, each one will remove the other's output.

I assume this is trying to remove old shortsums of the current file.

Stylus ignores sprite

Hello, I try to integrate sprite with my test express app and have some problems.

# app.coffee
if app.settings.env is "development"
    sprite.sprites
        path: './assets/blocks/',
        watch: true,
        (err, result) ->
            console.log result

Sprite is able to generate images, but watch option is ignored.

#app.styl
body
  sprite header bg

Is rendered to:

#app.css
body {
  sprite: header bg;
}

Modules info:

{
    "name": "express-coffee"
  , "version": "0.1.6"
  , "private": true
  , "dependencies": {
      "express": "3.x" 
    , "coffee-script": "1.4.0"
    , "jade": "0.26.0"
    , "connect-assets": "2.3.3"
    , "stylus": "0.27.x"
    , "nib": "0.9.0"
    , "markdown": "0.4.0"
    , "passport": "0.1.15"
    , "which": "1.0.5"
    , "mongoose": "3.5.3"
    , "wrench": "1.4.4"
  }, "scripts": {
    "install": "node node_modules/coffee-script/bin/cake build",
    "start": "node server.js",
    "test": "mocha --require should --compilers coffee:coffee-script --colors"
  }, "devDependencies": {
      "coffee-script": "*"
    , "mocha": "*"
    , "should": "*"
    , "supertest": "*"
    , "request": "*"
    , "supervisor": "*"
    , "docco-husky": "*"
    , "puts": "*"
    , "node-inspector": "*"
    , "ejs": "*"
  }, 
  "engines": {
    "node": "~0.8.x",
    "npm": "1.1.x"
  }
}

Try to run examples - vain

Trace after run /example/stylus.js

{ [TypeError: /Users/echuvelev/Development/hospital/express/node_modules/stylus/lib/functions/index.styl:286:14
   282|
   283|     &,
   284|     /$cache_placeholder_for_{$id}
   285|       $stylus_mixin_cache[$key] = $id
   286|       {block}
---------------------^
   287|

Cannot read property 'length' of undefined
    at "#smallSquare" (/Users/echuvelev/Development/hospital/express/node_modules/node-sprite/example/sprite.styl:2:1)
]
  lineno: 286,
  column: 14,
  filename: '/Users/echuvelev/Development/hospital/express/node_modules/stylus/lib/functions/index.styl',
  stylusStack: '    at "#smallSquare" (/Users/echuvelev/Development/hospital/express/node_modules/node-sprite/example/sprite.styl:2:1)',
  input: 'called-from = ()\n\nvendors = moz webkit o ms official\n\n// stringify the given arg\n\n-string(arg)\n  type(arg) + \' \' + arg\n\n// require a color\n\nrequire-color(color)\n  unless color is a \'color\'\n    error(\'RGB or HSL value expected, got a \' + -string(color))\n\n// require a unit\n\nrequire-unit(n)\n  unless n is a \'unit\'\n    error(\'unit expected, got a \' + -string(n))\n\n// require a string\n\nrequire-string(str)\n  unless str is a \'string\' or str is a \'ident\'\n    error(\'string expected, got a \' + -string(str))\n\n// Math functions\n\nabs(n) { math(n, \'abs\') }\nmin(a, b) { a < b ? a : b }\nmax(a, b) { a > b ? a : b }\n\n// Trigonometrics\nPI = -math-prop(\'PI\')\n\nradians-to-degrees(angle)\n  angle * (180 / PI)\n\ndegrees-to-radians(angle)\n  unit(angle * (PI / 180),\'\')\n\nsin(n)\n  n = degrees-to-radians(n) if unit(n) == \'deg\'\n  round(math(n, \'sin\'), 9)\n\ncos(n)\n  n = degrees-to-radians(n) if unit(n) == \'deg\'\n  round(math(n, \'cos\'), 9)\n\n// Rounding Math functions\n\nceil(n, precision = 0)\n  multiplier = 10 ** precision\n  math(n * multiplier, \'ceil\') / multiplier\n\nfloor(n, precision = 0)\n  multiplier = 10 ** precision\n  math(n * multiplier, \'floor\') / multiplier\n\nround(n, precision = 0)\n  multiplier = 10 ** precision\n  math(n * multiplier, \'round\') / multiplier\n\n// return the sum of the given numbers\n\nsum(nums)\n  sum = 0\n  sum += n for n in nums\n\n// return the average of the given numbers\n\navg(nums)\n  sum(nums) / length(nums)\n\n// return a unitless number, or pass through\n\nremove-unit(n)\n  if typeof(n) is "unit"\n    unit(n, "")\n  else\n    n\n\n// convert a percent to a decimal, or pass through\n\npercent-to-decimal(n)\n  if unit(n) is "%"\n    remove-unit(n) / 100\n  else\n    n\n\n// check if n is an odd number\n\nodd(n)\n  1 == n % 2\n\n// check if n is an even number\n\neven(n)\n  0 == n % 2\n\n// check if color is light\n\nlight(color)\n  lightness(color) >= 50%\n\n// check if color is dark\n\ndark(color)\n  lightness(color) < 50%\n\n// desaturate color by amount\n\ndesaturate(color, amount)\n  adjust(color, \'saturation\', - amount)\n\n// saturate color by amount\n\nsaturate(color = \'\', amount = 100%)\n  if color is a \'color\'\n    adjust(color, \'saturation\', amount)\n  else\n    unquote( "saturate(" + color + ")" )\n\n// darken by the given amount\n\ndarken(color, amount)\n  adjust(color, \'lightness\', - amount)\n\n// lighten by the given amount\n\nlighten(color, amount)\n  adjust(color, \'lightness\', amount)\n\n// decrease opacity by amount\n\nfade-out(color, amount)\n  color - rgba(black, percent-to-decimal(amount))\n\n// increase opacity by amount\n\nfade-in(color, amount)\n  color + rgba(black, percent-to-decimal(amount))\n\n// spin hue by a given amount\n\nspin(color, amount)\n  color + unit(amount, deg)\n\n// mix two colors by a given amount\n\nmix(color1, color2, weight = 50%)\n  unless weight in 0..100\n    error("Weight must be between 0% and 100%")\n\n  if length(color1) == 2\n    weight = color1[0]\n    color1 = color1[1]\n\n  else if length(color2) == 2\n    weight = 100 - color2[0]\n    color2 = color2[1]\n\n  require-color(color1)\n  require-color(color2)\n\n  p = unit(weight / 100, \'\')\n  w = p * 2 - 1\n\n  a = alpha(color1) - alpha(color2)\n\n  w1 = (((w * a == -1) ? w : (w + a) / (1 + w * a)) + 1) / 2\n  w2 = 1 - w1\n\n  channels = (red(color1) red(color2)) (green(color1) green(color2)) (blue(color1) blue(color2))\n  rgb = ()\n\n  for pair in channels\n    push(rgb, floor(pair[0] * w1 + pair[1] * w2))\n\n  a1 = alpha(color1) * p\n  a2 = alpha(color1) * (1 - p)\n  alpha = a1 + a2\n\n  rgba(rgb[0], rgb[1], rgb[2], alpha)\n\n// invert colors, leave alpha intact\n\ninvert(color = \'\')\n  if color is a \'color\'\n    rgba(#fff - color, alpha(color))\n  else\n    unquote( "invert(" + color + ")" )\n\n// give complement of the given color\n\ncomplement( color )\n  spin( color, 180 )\n\n// give grayscale of the given color\n\ngrayscale( color = \'\' )\n  if color is a \'color\'\n    desaturate( color, 100% )\n  else\n    unquote( "grayscale(" + color + ")" )\n\n// mix the given color with white\n\ntint( color, percent )\n  mix( white, color, percent )\n\n// mix the given color with black\n\nshade( color, percent )\n  mix( black, color, percent )\n\n// return the last value in the given expr\n\nlast(expr)\n  expr[length(expr) - 1]\n\n// return keys in the given pairs or object\n\nkeys(pairs)\n  ret = ()\n  if type(pairs) == \'object\'\n    for key in pairs\n      push(ret, key)\n  else\n    for pair in pairs\n      push(ret, pair[0]);\n  ret\n\n// return values in the given pairs or object\n\nvalues(pairs)\n  ret = ()\n  if type(pairs) == \'object\'\n    for key, val in pairs\n      push(ret, val)\n  else\n    for pair in pairs\n      push(ret, pair[1]);\n  ret\n\n// join values with the given delimiter\n\njoin(delim, vals...)\n  buf = \'\'\n  vals = vals[0] if length(vals) == 1\n  for val, i in vals\n    buf += i ? delim + val : val\n\n// add a CSS rule to the containing block\n\n// - This definition allows add-property to be used as a mixin\n// - It has the same effect as interpolation but allows users\n//   to opt for a functional style\n\nadd-property-function = add-property\nadd-property(name, expr)\n  if mixin\n    {name} expr\n  else\n    add-property-function(name, expr)\n\nprefix-classes(prefix)\n  -prefix-classes(prefix, block)\n\n// Caching mixin, use inside your functions to enable caching by extending.\n\n$stylus_mixin_cache = {}\ncache()\n  $key = (current-media() or \'no-media\') + \'__\' + called-from[0] + \'__\' + arguments\n  if $key in $stylus_mixin_cache\n    @extend {"$cache_placeholder_for_" + $stylus_mixin_cache[$key]}\n  else if \'cache\' in called-from\n    {block}\n  else\n    $id = length($stylus_mixin_cache)\n\n    &,\n    /$cache_placeholder_for_{$id}\n      $stylus_mixin_cache[$key] = $id\n      {block}\n' }
undefined
watching for file changes in './images' ...

Rebuild *.json file if it doesn't exist but image does

I just had the case that for some reason the spritesheet.png was available in the filesystem but the corresponding spritesheet.json wasn't (not commited). node-sprite ran over the thing and told me it processed all files properly and I naturally assumed there would be a .json file waiting for me. Yet unfortunately there was no .json file because node-sprite seems to not recreate a .json file if the .png exists.

Would it be posssible that node-sprite creates a .json file (and recreates the .png) if the .json file doesn't exist? It would be also okay to add a option for that?

Now: I know its the users problem if they decide to not push a .json file however: A chance less for human-error is a chance for a better life ;)

in case _getFiles got crash (in sprite.coffee)

I got an error when I was going to make spritesheet file. 'uncaught exception' occurred.
(can't remember exact condition)
In sprite.coffee file, '_getFiles' method made an error.

A simple 'if' check would protect it from error. Code goes below: (sprite.coffee)

104 104       _getFiles: (cb) ->
105 105         fs.readdir "#{@path}/#{@name}", (err, files) ->

106     -         files = files.filter (file) -> file.match /\.(png|gif|jpg|jpeg)$/
    106 +         if not err and files
    107 +           files = files.filter (file) -> file.match /\.(png|gif|jpg|jpeg)$/

107 108           cb err, files

Thanks for your contribution.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.