Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added radial-gradient incl svg and canvas #12

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion lib/nib.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ function plugin() {
style.define('has-canvas', nodes.true);

// gradients
style.define('create-gradient-image', gradient.create)
style.define('create-linear-gradient-image', gradient.createLinear)
style.define('create-radial-gradient-image', gradient.createRadial);
style.define('gradient-data-uri', gradient.dataURL)
style.define('gradient-data-uri-svg', gradient.dataURLSvg)
style.define('add-color-stop', gradient.addColorStop)

// color images
Expand Down
5 changes: 5 additions & 0 deletions lib/nib/config.styl
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ support-for-ie = true
*/

vendor-prefixes ?= webkit moz official

/*
* Default gradient formats.
*/
gradient-formats ?= png svg css
293 changes: 284 additions & 9 deletions lib/nib/gradients.styl
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

@import 'config'
@import 'util'

/*
* Replace the given str with val in the expr.
Expand Down Expand Up @@ -41,10 +42,10 @@ pos-in-stops(i, stops)

/*
* Normalize color stops:
*
*
* - (color pos) -> (pos color)
* - (color) -> (implied-pos color)
*
*
*/

normalize-stops(stops)
Expand Down Expand Up @@ -86,19 +87,19 @@ webkit-stop(color, pos)
*/

std-stop(color, pos)
'%s %s' % (color pos)
'%s %s' % (color pos)

/*
* Create a linear gradient with the given start position
* and variable number of color stops.
*
*
* Examples:
*
*
* background: linear-gradient(top, red, green, blue)
* background: linear-gradient(bottom, red, green 50%, blue)
* background: linear-gradient(bottom, red, 50% green, blue)
* background: linear-gradient(bottom, red, 50% green, 90% white, blue)
*
*
*/

linear-gradient(start, stops...)
Expand All @@ -123,10 +124,10 @@ linear-gradient(start, stops...)
stops = join-stops(stops, std-stop)
for prefix in vendor-prefixes
unless prefix == official
gradient = '-%s-linear-gradient(%s, %s)' % (prefix start stops)
gradient = '-%s-linear-gradient(%s, %s)' % (prefix start stops)
add-property(prop, replace(val, '__CALL__', gradient))

// standard
// standard
'linear-gradient(%s, %s)' % (start stops)

/*
Expand All @@ -140,7 +141,281 @@ linear-gradient-image(start, stops...)
error('gradient image size required') unless start[0] is a 'unit'
size = start[0]
start = start[1] or 'top'
grad = create-gradient-image(size, start)
grad = create-linear-gradient-image(size, start)
stops = normalize-stops(stops)
add-color-stop(grad, stop[0], stop[1]) for stop in stops
'url(%s)' % gradient-data-uri(grad)

/*
* Create a radial gradient with the given center position, size and
* variable number of color stops.
*
* Examples:
*
* background: radial-gradient(white, black)
* background: radial-gradient(top left, 35px 35px, red, yellow, green)
* background: radial-gradient(green, yellow 100px)
*/
radial-gradient(start, size, stops..., formats=gradient-formats)
args = normalize-radial-gradient(start, size, stops)
start = args[0]
size = args[1]
stops = args[2]

error('color stops required') unless length(stops)

prop = current-property[0]
val = current-property[1]

if radius
// canvas aka IE 5.5, 6, 7 & 8
if has-canvas and png in formats
img = radial-gradient-image(start, size, stops)
add-property(prop, replace(val, '__CALL__', img))

// svg aka IE 9
if svg in formats
svg = radial-gradient-svg(start, size, stops)
add-property(prop, replace(val, '__CALL__', svg))

// css
if css in formats
// webkit legacy
if webkit in vendor-prefixes
webkit-legacy = '-webkit-gradient(radial, %s, 0, %s, %s, %s)' % (start start radius join-stops(stops, webkit-stop))
add-property(prop, replace(val, '__CALL__', webkit-legacy))

if css in formats
// webkit and mozilla
stops = join-stops(stops, std-stop)
for prefix in vendor-prefixes
unless prefix == official
gradient = '-%s-radial-gradient(%s, %s, %s)' % (prefix start size stops)
add-property(prop, replace(val, '__CALL__', gradient))

// standard
'radial-gradient(%s, %s, %s)' % (start size stops)

/*
* Create a radial gradient image with the given center position
* and variable number of color stops.
*/
radial-gradient-image(start, size, stops...)
error('node-canvas is required for radial-gradient-image()') unless has-canvas
stops = stops[0] if length(stops) == 1
error('color stops required') unless length(stops)

// create the radial gradient image
grad = create-radial-gradient-image(start[0], start[1], size[0], size[1], size[2], size[3])
add-color-stop(grad, stop[0], stop[1]) for stop in stops
'url(%s)' % gradient-data-uri(grad)

/*
* Create a radial gradient SVG with the given center position
* and variable number of color stops.
*/
radial-gradient-svg(start, size, stops...)
stops = stops[0] if length(stops) == 1
error('color stops required') unless length(stops)

svg = '<?xml version="1.0" encoding="utf-8"?>'
svg += '<svg version="1.1" xmlns="http://www.w3.org/2000/svg">'
svg += '<defs>'
svg += '<radialGradient id="grad" gradientUnits="userSpaceOnUse" cx="%spx" cy="%spx" r="%s">' % (start[0] start[1] size[0])
for stop in stops
svg += '<stop offset="%s" stop-color="%s"/>' % (stop[0] stop[1])
svg += '</radialGradient>'
svg += '</defs>'
svg += '<rect x="0" y="0" width="100%" height="100%" fill="url(#grad)" />'
svg += '</svg>'

'url(%s)' % gradient-data-uri-svg(svg)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exposed method

gradient-data-uri-svg

is a really good idea, although I would just make it _data-uri-svg_ as it can take any SVG file, not just gradients. I like the idea of being able to write SVG in my stylus files for specific use cases to my design to generate a data URI.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I agree some kind of generalized SVG support would be nice.

SVG could be used for lots of things, like icons, border images, list style images, even logos etc obviously.

Composing short SVGs in stylus could be handy, it might be nice if we could avoid writing the doctype boilerplate per call.

I think stylus might already natively supporting in-lining external SVG files. It might also be a nice feature in nib to be able to inline Jade files automatically compiled to SVG data URIs with the ability to pass view variables from stylus.


/*
*
* Examples:
*
* (green, blue)
* (top, green blue)
* (top left, 50px 50px 150px, green, blue)
* (circle cover 150px 100px, green, blue 30%, yellow 90%)
* See test/cases/gradients.normalize-radial-gradient.styl
*
* Returns:
*
* ((bg-x bg-y) ((r-x r-y) (box-x box-y) (sizing shape)) ((offset rgba)...))
*
* TODO
*
* BROKEN
*
* more tests, refactor
*
* breakup into smaller functions
*
* support relative sizes e.g. em
* em requires knowledge of current font-size
* % of size of the containing box etc
*
* ellipse sizing (currently only circles)
*/
normalize-radial-gradient(start, size, stops...)
if is-color-stop(size)
unshift(stops, size)

if is-radial-gradient-size(start)
size = start
start = center
else
size = circle cover

if is-color-stop(start)
unshift(stops, start)
start = center

stops = normalize-stops(stops)

error('color stops required') unless length(stops)

p(start)
p(size)
p(stops)

// normalize box sizing
if (length(size)) in (3 4)
// explicit box sizing
error('box width must be a unit') unless size[2] is a 'unit'
box-x = size[2]
if (length(size)) is 4
error('box height must be a unit') unless size[3] is a 'unit'
box-y = size[3]
else
box-y = box-x
else
// implicit box sizing
box-x = box-y = false

// normalize radius
if size[0] is a 'unit'
// explicit radius
shape = sizing = false
radius-x = size[0]
if length(size) is 2
error('vertical radius must be a unit') unless size[1] is a 'unit'
radius-y = size[1]
radius-y ?= radius-x
else if size[0] is a 'ident'
// implicit radius
radius-x = radius-y = false
error('shape must be circle or ellipse') unless size[0] in (circle ellipse)
shape = size[0]
if (length(size)) in (2 3 4)
if size[1] is a 'ident'
error('invalid size') unless size[1] in (closest-side closest-corner farthest-side farthest-corner contain cover)
sizing = size[1]
sizing = closest-side if sizing is contain
sizing = farthest-corner if sizing is cover
else
sizing = cover

// fake box sizing from other available info
if not box-x
if radius-x
// assume the radius is the box
box-x = box-y = radius-x
else if is-color-stop(last(stops))
if unit(last(stops)[0]) is 'px'
p('HAI!')
// if there is absolutely no sizing info resort to final color stop
box-x = box-y = radius-x = radius-y = last(stops)[0]

// normalize start position
if length(start) is 1
if start is a 'ident'
start = center start if start in (top bottom center)
start = start center if start in (left right)
else if start is a 'unit'
start = start start
else if start[0] in (top bottom)
start = start[1] start[0]

// calculate start position in pixels
if start[0] is a 'unit'
if unit(start[0]) is 'px'
start-x = start[0]
if start[1] is a 'unit'
if unit(start[1]) is 'px'
start-y = start[1]

start-x = 0px if start[0] is 'left'
if box-x
start-x = box-x if start[0] is 'right'
start-x = (box-x / 2) if start[0] is 'center'
if start[0] is a 'unit'
if unit(start[0]) is '%'
start-x = unit((start[0] / 100) * box-x, 'px')
start-y = 0px if start[1] is 'top'
if box-y
start-y = box-y if start[1] is 'bottom'
start-y = (box-y / 2) if start[1] is 'center'
if start[1] is a 'unit'
if unit(start[1]) is '%'
start-y = unit((start[1] / 100) * box-y, 'px')

if box-x and shape and sizing
// calculate the radius from the box
if shape is circle
if sizing is closest-side
radius-x = radius-y = -min((start-x start-y (box-x - start-x) (box-y - start-y)))
else if sizing is farthest-side
radius-x = radius-y = -max((start-x start-y (box-x - start-x) (box-y - start-y)))
else if shape is ellipse
if sizing is closest-side
radius-x = -min((start-x (box-x - start-x)))
radius-y = -min((start-y (box-y - start-y)))
else if sizing is farthest-side
radius-x = -max((start-x (box-x - start-x)))
radius-y = -max((start-x (box-y - start-y)))
// TODO closest-corner, farthest corner

p(((start-x start-y) (radius-x radius-y box-x box-y) stops))

((start-x start-y) (radius-x radius-y box-x box-y) stops)
/*
* Check if the given val is a valid radial gradient size/shape
*
* Examples:
*
* 50px 25px 150px 100px
* x-radius y-radius box-x box-y
*
* 50% 50% 150px
* x-radius y-radius box-xy
*
* circle cover 150px
* ident ident box-xy
*
* TODO more examples, including valid CSS without enough infos for image
* TODO refactor, gives false positives
*/
is-radial-gradient-size(val)
if (length(val)) > 2
return true
else if val is a 'ident'
if val in (circle ellipse closest-side closest-corner farthest-side farthest-corner contain cover)
return true
false

/*
* Check if the given val is a valid gradient color stop
*/
is-color-stop(val)
if length(val) is 1 and val is a 'rgba'
true
else if length(val) is 2
if (val[0] is a 'unit' and val[1] is a 'rgba') or (val[0] is a 'rgba' and val[1] is a 'unit')
true
else
false
else
false
1 change: 1 addition & 0 deletions lib/nib/index.styl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

@import 'util'
@import 'vendor'
@import 'text'
@import 'reset'
Expand Down
20 changes: 20 additions & 0 deletions lib/nib/util.styl
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/*
* Returns the minimum value in list.
*/
-min(list)
minimum = list[0]
for val in list
if val < minimum
minimum = val
minimum

/*
* Returns the maximum value in list.
*/
-max(list)
maximum = list[0]
for val in list
if val > maximum
maximum = val
maximum
Loading