├── .gitignore
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── demo
├── codepen-template
│ ├── codepen.js
│ └── package.json
├── css
│ ├── styles.css
│ └── styles.pcss
├── gulpfile.js
├── index.html
├── package-lock.json
└── package.json
├── index.js
├── index.test.js
├── lib
├── colorStops.js
└── helpers.js
├── package-lock.json
└── package.json
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | yarn-error.log
4 | .DS_Store
5 |
6 | # Browserify bundles
7 | *.bundle.js
8 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | .npmignore
2 | .gitignore
3 | .editorconfig
4 |
5 | node_modules/
6 | npm-debug.log
7 | yarn.lock
8 |
9 | *.test.js
10 | .travis.yml
11 |
12 | # Demo and assets folder
13 | demo
14 | assets
15 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - stable
4 | sudo: false
5 | branches:
6 | only:
7 | - master
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Andreas Larsen
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PostCSS Easing Gradients
2 |
3 | [![NPM Version][npm-img]][npm]
4 | [![NPM Monthly Downloads][dm-img]][npm]
5 | [![Build Status][ci-img]][ci]
6 | [![Dependency status][dpd-img]][dpd]
7 |
8 | [![MIT License][mit-img]][mit]
9 | [![Code Style: Prettier][prt-img]][prt]
10 | [![Follow Larsenwork on Twitter][twt-img]][twt]
11 |
12 | [PostCSS](https://github.com/postcss/postcss) plugin to create smooth linear-gradients that approximate easing functions.
13 | Visual examples and online editor on [larsenwork.com/easing-gradients](https://larsenwork.com/easing-gradients/)
14 |
15 | ## Code Examples
16 |
17 | ```css
18 | .cubic-bezier {
19 | background: linear-gradient(to bottom, black, cubic-bezier(0.48, 0.3, 0.64, 1), transparent);
20 | /* => */
21 | background: linear-gradient(
22 | to bottom,
23 | hsl(0, 0%, 0%),
24 | hsla(0, 0%, 0%, 0.94505) 7.9%,
25 | hsla(0, 0%, 0%, 0.88294) 15.3%,
26 | hsla(0, 0%, 0%, 0.81522) 22.2%,
27 | hsla(0, 0%, 0%, 0.7426) 28.7%,
28 | hsla(0, 0%, 0%, 0.66692) 34.8%,
29 | hsla(0, 0%, 0%, 0.58891) 40.6%,
30 | hsla(0, 0%, 0%, 0.50925) 46.2%,
31 | hsla(0, 0%, 0%, 0.42866) 51.7%,
32 | hsla(0, 0%, 0%, 0.34817) 57.2%,
33 | hsla(0, 0%, 0%, 0.2693) 62.8%,
34 | hsla(0, 0%, 0%, 0.19309) 68.7%,
35 | hsla(0, 0%, 0%, 0.12126) 75.2%,
36 | hsla(0, 0%, 0%, 0.05882) 82.6%,
37 | hsla(0, 0%, 0%, 0.01457) 91.2%,
38 | hsla(0, 0%, 0%, 0)
39 | );
40 | }
41 |
42 | .ease {
43 | background: linear-gradient(green, ease, red);
44 | /* => */
45 | background: linear-gradient(
46 | hsl(120, 100%, 25.1%),
47 | hsl(88.79, 100%, 24.28%) 7.8%,
48 | hsl(69.81, 100%, 23.14%) 13.2%,
49 | hsl(53.43, 100%, 24.55%) 17.6%,
50 | hsl(42.52, 100%, 28.9%) 21.7%,
51 | hsl(34.96, 100%, 32.64%) 25.8%,
52 | hsl(29.1, 100%, 35.96%) 30.2%,
53 | hsl(24.26, 100%, 38.94%) 35.1%,
54 | hsl(20.14, 100%, 41.56%) 40.6%,
55 | hsl(16.47, 100%, 43.87%) 46.9%,
56 | hsl(13.13, 100%, 45.83%) 54.1%,
57 | hsl(10.07, 100%, 47.42%) 62.2%,
58 | hsl(7.23, 100%, 48.62%) 71.1%,
59 | hsl(4.6, 100%, 49.43%) 80.6%,
60 | hsl(2.16, 100%, 49.87%) 90.5%,
61 | hsl(0, 100%, 50%)
62 | );
63 | }
64 |
65 | .steps {
66 | background: linear-gradient(to right, green, steps(4, skip-none), red);
67 | /* => */
68 | background: linear-gradient(
69 | to right,
70 | hsl(120, 100%, 25.1%),
71 | hsl(120, 100%, 25.1%) 25%,
72 | hsl(42.59, 100%, 28.87%) 25%,
73 | hsl(42.59, 100%, 28.87%) 50%,
74 | hsl(21.3, 100%, 40.82%) 50%,
75 | hsl(21.3, 100%, 40.82%) 75%,
76 | hsl(0, 100%, 50%) 75%,
77 | hsl(0, 100%, 50%)
78 | );
79 | }
80 |
81 | .radial {
82 | background: radial-gradient(circle at top right, red, ease-in-out, blue);
83 | /* => */
84 | background: radial-gradient(
85 | circle at top right,
86 | hsl(0, 100%, 50%),
87 | hsl(353.5, 100%, 49.71%) 7.7%,
88 | hsl(347.13, 100%, 48.89%) 14.8%,
89 | hsl(341.1, 100%, 47.69%) 21%,
90 | hsl(335.24, 100%, 46.22%) 26.5%,
91 | hsl(329.48, 100%, 44.57%) 31.4%,
92 | hsl(323.63, 100%, 42.76%) 35.9%,
93 | hsl(317.56, 100%, 40.82%) 40.1%,
94 | hsl(310.92, 100%, 38.7%) 44.2%,
95 | hsl(303.81, 100%, 36.49%) 48.1%,
96 | hsl(296, 100%, 36.55%) 52%,
97 | hsl(288.73, 100%, 38.81%) 56%,
98 | hsl(282.14, 100%, 40.92%) 60.1%,
99 | hsl(276.09, 100%, 42.84%) 64.3%,
100 | hsl(270.27, 100%, 44.64%) 68.8%,
101 | hsl(264.54, 100%, 46.28%) 73.7%,
102 | hsl(258.7, 100%, 47.74%) 79.2%,
103 | hsl(252.68, 100%, 48.92%) 85.4%,
104 | hsl(246.32, 100%, 49.72%) 92.5%,
105 | hsl(240, 100%, 50%)
106 | );
107 | }
108 | ```
109 |
110 |
111 |
112 | ## Syntax
113 |
114 | Currently a subset of the [full syntax](https://github.com/w3c/csswg-drafts/issues/1332#issuecomment-299990698) is supported:
115 |
116 | ```xml
117 | linear-gradient(
118 | [ ,]?
119 | ,
120 | ,
121 |
122 | )
123 | ```
124 |
125 | The steps syntax is also being figured out and currently [this](https://github.com/w3c/csswg-drafts/issues/1680#issuecomment-361550637) is supported.
126 |
127 |
128 |
129 | ## Usage
130 |
131 | ```js
132 | postcss([require('postcss-easing-gradients')])
133 | ```
134 |
135 | See [PostCSS Usage](https://github.com/postcss/postcss#usage) docs for examples for your environment.
136 |
137 |
138 |
139 | ## Options
140 |
141 | ### colorStops: 15
142 |
143 | is the default. A lower number creates a more "low poly" gradient with less code but a higher risk of banding.
144 |
145 | ### alphaDecimals: 5
146 |
147 | is the default. A lower number can result in banding.
148 |
149 | ### colorMode: 'lrgb'
150 |
151 | is the default color space used for interpolation and is closest to what most browsers use. Other options are `'rgb', 'hsl', 'lab' and 'lch'` as per [chromajs documentation](http://gka.github.io/chroma.js/#chroma-mix)
152 |
153 | [ci-img]: https://img.shields.io/travis/larsenwork/postcss-easing-gradients.svg?branch=master&longCache=true&style=flat-square
154 | [ci]: https://travis-ci.org/larsenwork/postcss-easing-gradients
155 | [npm-img]: https://img.shields.io/npm/v/postcss-easing-gradients.svg?longCache=true&style=flat-square
156 | [npm]: https://www.npmjs.com/package/postcss-easing-gradients
157 | [dm-img]: https://img.shields.io/npm/dm/postcss-easing-gradients.svg?longCache=true&style=flat-square
158 | [dpd-img]: https://img.shields.io/david/larsenwork/postcss-easing-gradients.svg?longCache=true&style=flat-square
159 | [dpd]: https://david-dm.org/larsenwork/postcss-easing-gradients
160 | [prt-img]: https://img.shields.io/badge/code_style-prettier-ff69b4.svg?longCache=true&style=flat-square
161 | [prt]: https://github.com/prettier/prettier
162 | [mit-img]: https://img.shields.io/github/license/larsenwork/postcss-easing-gradients.svg?longCache=true&style=flat-square
163 | [mit]: https://github.com/larsenwork/postcss-easing-gradients/blob/master/LICENSE
164 | [twt-img]: https://img.shields.io/twitter/follow/larsenwork.svg?label=follow+larsenwork&longCache=true&style=flat-square
165 | [twt]: https://twitter.com/larsenwork
166 |
--------------------------------------------------------------------------------
/demo/codepen-template/codepen.js:
--------------------------------------------------------------------------------
1 | const plugin = require('postcss-easing-gradients')
2 | const styles = document.head.getElementsByTagName('style')
3 |
4 | const updateStyle = style => {
5 | plugin.process(style.textContent).then(result => {
6 | style.textContent = result.css
7 | }, console.error)
8 | }
9 |
10 | if (styles.length) {
11 | Array.prototype.forEach.call(styles, updateStyle)
12 | }
13 |
14 | new MutationObserver(mutations =>
15 | mutations.forEach(mutation =>
16 | Array.prototype.filter
17 | .call(mutation.addedNodes || [], node => node.nodeName === 'STYLE')
18 | .forEach(updateStyle)
19 | )
20 | ).observe(document.head, {
21 | childList: true,
22 | })
23 |
--------------------------------------------------------------------------------
/demo/codepen-template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "codepen-template",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "codepen.js",
6 | "scripts": {
7 | "browserify":
8 | "browserify codepen.js | babel --presets=env | uglifyjs --compress --mangle > codepen.bundle.js"
9 | },
10 | "author": "Andreas",
11 | "license": "MIT",
12 | "devDependencies": {
13 | "babel-cli": "^6.24.1",
14 | "babel-preset-env": "^1.4.0",
15 | "browserify": "^14.3.0",
16 | "postcss-easing-gradients": "^1.2.2",
17 | "uglify-js": "^2.8.22"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/demo/css/styles.css:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | padding: 0;
5 | margin: 0;
6 | }
7 |
8 | body {
9 | /*background-image: linear-gradient(to right, hsl(100, 100%, 50%) 49%, hsl(200, 100%, 50%) 50%);*/
10 | }
11 |
12 | .gradient {
13 | background: linear-gradient(to right, hsl(120, 100%, 25.1%), hsl(120, 100%, 25.1%) 25%, hsl(42.59, 100%, 28.87%) 25%, hsl(42.59, 100%, 28.87%) 50%, hsl(21.3, 100%, 40.82%) 50%, hsl(21.3, 100%, 40.82%) 75%, hsl(0, 100%, 50%) 75%, hsl(0, 100%, 50%));
14 | background-position: center;
15 | background-repeat: no-repeat;
16 | background-size: cover;
17 | height: 50vh;
18 | width: 100vw;
19 | }
20 |
21 | .gradient--easing {
22 | background: radial-gradient(circle at top right, hsl(0, 100%, 50%), hsl(351.5, 100%, 49.51%) 9.99%, hsl(343.03, 100%, 48.11%) 19.07%, hsl(334.18, 100%, 45.93%) 27.44%, hsl(324.5, 100%, 43.03%) 35.26%, hsl(313.41, 100%, 39.49%) 42.72%, hsl(300, 100%, 35.36%) 50%, hsl(286.59, 100%, 39.49%) 57.28%, hsl(275.5, 100%, 43.03%) 64.74%, hsl(265.82, 100%, 45.93%) 72.56%, hsl(256.97, 100%, 48.11%) 80.93%, hsl(248.5, 100%, 49.51%) 90.01%, hsl(240, 100%, 50%));
23 | width: 100vw;
24 | opacity: 1;
25 | }
26 |
--------------------------------------------------------------------------------
/demo/css/styles.pcss:
--------------------------------------------------------------------------------
1 | *,
2 | *:before,
3 | *:after {
4 | padding: 0;
5 | margin: 0;
6 | }
7 |
8 | body {
9 | /*background-image: linear-gradient(to right, hsl(100, 100%, 50%) 49%, hsl(200, 100%, 50%) 50%);*/
10 | }
11 |
12 | .gradient {
13 | background: linear-gradient(to right, green, steps(4, skip-none), red);
14 | background-position: center;
15 | background-repeat: no-repeat;
16 | background-size: cover;
17 | height: 50vh;
18 | width: 100vw;
19 | }
20 |
21 | .gradient--easing {
22 | background: radial-gradient(circle at top right, red, ease-in-out, blue);
23 | width: 100vw;
24 | opacity: 1;
25 | }
26 |
--------------------------------------------------------------------------------
/demo/gulpfile.js:
--------------------------------------------------------------------------------
1 | const browserSync = require('browser-sync').create()
2 | const gulp = require('gulp')
3 | const gulpPostcss = require('gulp-postcss')
4 | const gulpRename = require('gulp-rename')
5 | const easingGradient = require('postcss-easing-gradients')
6 |
7 | const paths = {
8 | base: './',
9 | styles: {
10 | src: './css/*.pcss',
11 | dest: './css',
12 | },
13 | html: {
14 | src: './*.html',
15 | },
16 | }
17 |
18 | function serve(done) {
19 | browserSync.init({
20 | server: { baseDir: paths.base },
21 | open: false,
22 | })
23 | done()
24 | }
25 |
26 | function reload(done) {
27 | browserSync.reload()
28 | done()
29 | }
30 |
31 | function watch() {
32 | gulp.watch(paths.styles.src, gulp.series(css, reload))
33 | gulp.watch(paths.html.src).on('change', browserSync.reload)
34 | }
35 |
36 | function css() {
37 | return gulp
38 | .src(paths.styles.src)
39 | .pipe(
40 | gulpPostcss([
41 | easingGradient({
42 | // Default settings
43 | precision: 0.1,
44 | alphaDecimals: 5,
45 | }),
46 | ])
47 | )
48 | .pipe(gulpRename({ extname: '.css' }))
49 | .pipe(gulp.dest(paths.styles.dest))
50 | }
51 |
52 | gulp.task('default', gulp.series(css, serve, watch))
53 |
54 | gulp.task('style', gulp.series(css))
55 |
--------------------------------------------------------------------------------
/demo/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | CSS Easing Gradients
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/demo/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pretty_css_gradients",
3 | "version": "2.0.0",
4 | "description": "Using postcss-easing-gradients to create the smoothest gradients.",
5 | "main": "index.js",
6 | "scripts": {
7 | "gulp": "./node_modules/gulp/bin/gulp.js"
8 | },
9 | "author": "Andreas Larsen",
10 | "license": "MIT",
11 | "devDependencies": {
12 | "browser-sync": "^2.24.7",
13 | "easing-coordinates": "^2.0.0",
14 | "gulp": "^4.0.0",
15 | "gulp-postcss": "^8.0.0",
16 | "gulp-rename": "^1.4.0",
17 | "postcss-easing-gradients": "^3.0.1"
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const easingCoordinates = require('easing-coordinates')
2 | const postcss = require('postcss')
3 | const valueParser = require('postcss-value-parser')
4 | const getColorStops = require('./lib/colorStops.js')
5 | const helpers = require('./lib/helpers.js')
6 |
7 | /**
8 | * The easing gradient function is a postcss plugin which supports the in /.helpers mentioned gradient types.
9 | */
10 | module.exports = postcss.plugin('easing-gradient', (options = {}) => {
11 | if (!options.stops) {
12 | options.stops = 13
13 | }
14 | return function(css) {
15 | css.walkRules(rule => {
16 | rule.walkDecls(decl => {
17 | // If declaration value contains a -gradient.
18 | if (decl.value.includes('-gradient')) {
19 | // Parse the declaration and walk through the nodes - https://github.com/TrySound/postcss-value-parser.
20 | const parsedValue = valueParser(decl.value)
21 | parsedValue.walk(node => {
22 | // Only modify gradient as the value can contain more e.g. 'linear-gradient(black, pink) center'.
23 | if (node.value === 'linear-gradient' || node.value === 'radial-gradient') {
24 | // Get a sensible array of gradient parameters where e.g. a function is split into multiple array items
25 | const gradientParams = valueParser
26 | .stringify(helpers.divToSemiColon(node.nodes))
27 | .split(';')
28 | .map(str => str.trim())
29 |
30 | gradientParams.forEach((param, i) => {
31 | if (helpers.isTimingFunction(param)) {
32 | try {
33 | const colors = [gradientParams[i - 1], gradientParams[i + 1]]
34 | const coordinates = easingCoordinates.easingCoordinates(
35 | param,
36 | options.stops - 1
37 | )
38 | const colorStops = getColorStops(
39 | colors,
40 | coordinates,
41 | options.alphaDecimals,
42 | options.colorMode
43 | )
44 | // Update node
45 | node.type = 'word'
46 | // Assume if it has 4 params it's because the first one is the direction
47 | if (gradientParams.length === 4) {
48 | node.value = `${node.value}(${gradientParams[0]}, ${colorStops.join(', ')})`
49 | } else {
50 | node.value = `${node.value}(${colorStops.join(', ')})`
51 | }
52 | // Update our declaration value
53 | decl.value = parsedValue.toString()
54 | } catch (error) {
55 | console.log(helpers.errorMsg(decl.value))
56 | }
57 | }
58 | })
59 | }
60 | })
61 | }
62 | })
63 | })
64 | }
65 | })
66 |
--------------------------------------------------------------------------------
/index.test.js:
--------------------------------------------------------------------------------
1 | const postcss = require('postcss')
2 | const plugin = require('./')
3 |
4 | function run(input, output, opts) {
5 | return postcss([plugin(opts)])
6 | .process(input, { from: null })
7 | .then(result => {
8 | expect(result.css).toEqual(output)
9 | expect(result.warnings()).toHaveLength(0)
10 | })
11 | }
12 |
13 | /* eslint-disable max-len */
14 |
15 | /**
16 | * Default output
17 | */
18 | it('create a steps gradient with direction', () => {
19 | return run(
20 | 'a{ background: linear-gradient(to right, green, steps(4, skip-none), red); }',
21 | 'a{ background: linear-gradient(to right, hsl(120, 100%, 25.1%), hsl(120, 100%, 25.1%) 25%, hsl(42.59, 100%, 28.87%) 25%, hsl(42.59, 100%, 28.87%) 50%, hsl(21.3, 100%, 40.82%) 50%, hsl(21.3, 100%, 40.82%) 75%, hsl(0, 100%, 50%) 75%, hsl(0, 100%, 50%)); }',
22 | {}
23 | )
24 | })
25 | it('create a cubic bezier gradient with direction', () => {
26 | return run(
27 | 'a{ background: linear-gradient(to right, black, cubic-bezier(0.48, 0.30, 0.64, 1.00), transparent); }',
28 | 'a{ background: linear-gradient(to right, hsl(0, 0%, 0%), hsla(0, 0%, 0%, 0.9173) 11.36%, hsla(0, 0%, 0%, 0.82176) 21.57%, hsla(0, 0%, 0%, 0.71719) 30.81%, hsla(0, 0%, 0%, 0.60741) 39.26%, hsla(0, 0%, 0%, 0.49624) 47.09%, hsla(0, 0%, 0%, 0.3875) 54.5%, hsla(0, 0%, 0%, 0.28501) 61.66%, hsla(0, 0%, 0%, 0.19259) 68.74%, hsla(0, 0%, 0%, 0.11406) 75.94%, hsla(0, 0%, 0%, 0.05324) 83.43%, hsla(0, 0%, 0%, 0.01395) 91.39%, hsla(0, 0%, 0%, 0)); }',
29 | {}
30 | )
31 | })
32 | it('create an ease gradient with direction', () => {
33 | return run(
34 | 'a{ background: linear-gradient(to right, green, ease, red); }',
35 | 'a{ background: linear-gradient(to right, hsl(120, 100%, 25.1%), hsl(95.38, 100%, 24.58%) 5.79%, hsl(78.24, 100%, 23.69%) 10.88%, hsl(60.53, 100%, 22.47%) 15.63%, hsl(45.6, 100%, 27.55%) 20.37%, hsl(35.49, 100%, 32.35%) 25.46%, hsl(27.94, 100%, 36.66%) 31.25%, hsl(21.9, 100%, 40.44%) 38.08%, hsl(16.79, 100%, 43.67%) 46.3%, hsl(12.26, 100%, 46.31%) 56.25%, hsl(8.08, 100%, 48.29%) 68.29%, hsl(4.05, 100%, 49.55%) 82.75%, hsl(0, 100%, 50%)); }',
36 | {}
37 | )
38 | })
39 | it('create an ease radial-gradient', () => {
40 | return run(
41 | 'a{ background: radial-gradient(circle at top right, red, ease-in-out, blue); }',
42 | 'a{ background: radial-gradient(circle at top right, hsl(0, 100%, 50%), hsl(351.5, 100%, 49.51%) 9.99%, hsl(343.03, 100%, 48.11%) 19.07%, hsl(334.18, 100%, 45.93%) 27.44%, hsl(324.5, 100%, 43.03%) 35.26%, hsl(313.41, 100%, 39.49%) 42.72%, hsl(300, 100%, 35.36%) 50%, hsl(286.59, 100%, 39.49%) 57.28%, hsl(275.5, 100%, 43.03%) 64.74%, hsl(265.82, 100%, 45.93%) 72.56%, hsl(256.97, 100%, 48.11%) 80.93%, hsl(248.5, 100%, 49.51%) 90.01%, hsl(240, 100%, 50%)); }',
43 | {}
44 | )
45 | })
46 |
47 | /**
48 | * Output with custom settings
49 | */
50 | it('create a cubic bezier gradient with 1 alphaDecimal', () => {
51 | return run(
52 | 'a{ background: linear-gradient(black, cubic-bezier(0.48, 0.30, 0.64, 1.00), transparent); }',
53 | 'a{ background: linear-gradient(hsl(0, 0%, 0%), hsla(0, 0%, 0%, 0.9) 11.36%, hsla(0, 0%, 0%, 0.8) 21.57%, hsla(0, 0%, 0%, 0.7) 30.81%, hsla(0, 0%, 0%, 0.6) 39.26%, hsla(0, 0%, 0%, 0.5) 47.09%, hsla(0, 0%, 0%, 0.4) 54.5%, hsla(0, 0%, 0%, 0.3) 61.66%, hsla(0, 0%, 0%, 0.2) 68.74%, hsla(0, 0%, 0%, 0.1) 75.94%, hsla(0, 0%, 0%, 0.1) 83.43%, hsla(0, 0%, 0%, 0) 91.39%, hsla(0, 0%, 0%, 0)); }',
54 | { alphaDecimals: 1 }
55 | )
56 | })
57 | it('create a cubic-bezier gradient with 5 stops', () => {
58 | return run(
59 | 'a{ background: linear-gradient(black, cubic-bezier(0.48, 0.30, 0.64, 1.00), transparent); }',
60 | 'a{ background: linear-gradient(hsl(0, 0%, 0%), hsla(0, 0%, 0%, 0.71719) 30.81%, hsla(0, 0%, 0%, 0.3875) 54.5%, hsla(0, 0%, 0%, 0.11406) 75.94%, hsla(0, 0%, 0%, 0)); }',
61 | { stops: 5 }
62 | )
63 | })
64 | it('create an ease gradient with hsl colorMode', () => {
65 | return run(
66 | 'a{ background: linear-gradient(to right, green, ease, red); }',
67 | 'a{ background: linear-gradient(to right, hsl(120, 100%, 25.1%), hsl(115.04, 100%, 26.08%) 5.79%, hsl(106.9, 100%, 27.84%) 10.88%, hsl(96.08, 100%, 30%) 15.63%, hsl(83.71, 100%, 32.75%) 20.37%, hsl(69.61, 100%, 35.49%) 25.46%, hsl(55.71, 100%, 38.43%) 31.25%, hsl(41.52, 100%, 41.37%) 38.08%, hsl(28.53, 100%, 44.12%) 46.3%, hsl(16.96, 100%, 46.47%) 56.25%, hsl(8.05, 100%, 48.24%) 68.29%, hsl(2.13, 100%, 49.61%) 82.75%, hsl(0, 100%, 50%)); }',
68 | { colorMode: 'hsl' }
69 | )
70 | })
71 | /**
72 | * Ignore incorrect/unsuported input
73 | */
74 | it('ignore unsuported gradients', () => {
75 | return run(
76 | 'a{ background: linear-gradient(black, funky-ease, transparent); }',
77 | 'a{ background: linear-gradient(black, funky-ease, transparent); }',
78 | {}
79 | )
80 | })
81 | it('ignore gradients with color stop locations set', () => {
82 | return run(
83 | 'a{ background: linear-gradient(black 20px, cubic-bezier(0.48, 0.30, 0.64, 1.00), transparent); }',
84 | 'a{ background: linear-gradient(black 20px, cubic-bezier(0.48, 0.30, 0.64, 1.00), transparent); }',
85 | {}
86 | )
87 | })
88 |
89 | /**
90 | * Fallback to linear gradient when incorrect transition functions
91 | */
92 | it('ignore gradients with incorrect transition function syntax set', () => {
93 | return run(
94 | 'a{ background: linear-gradient(black, cubic-bezier(0.48, 0.30, 0.64), transparent); }',
95 | 'a{ background: linear-gradient(black, cubic-bezier(0.48, 0.30, 0.64), transparent); }',
96 | {}
97 | )
98 | })
99 |
--------------------------------------------------------------------------------
/lib/colorStops.js:
--------------------------------------------------------------------------------
1 | const chroma = require('chroma-js')
2 | const helpers = require('./helpers.js')
3 |
4 | /**
5 | * Calculate the color stops based on start+stopColor in an array and easingType
6 | * @param {array} colors Two colors in an array
7 | * @param {array} coordinates An array of coordinates (object with x and y keys)
8 | * @param {number} alphaDecimals How many decimals should be on the returned color values
9 | * @param {string} colorMode Color space used for color interpolation http://gka.github.io/chroma.js/#chroma-mix
10 | * @returns {array} An array of colorstops (a string with color and position)
11 | */
12 | module.exports = (colors, coordinates, alphaDecimals = 5, colorMode = 'lrgb') => {
13 | const colorStops = []
14 | colors = helpers.transparentFix(colors)
15 | coordinates.forEach(coordinate => {
16 | const ammount = coordinate.y
17 | const percent = coordinate.x * 100
18 | let color = chroma.mix(colors[0], colors[1], ammount, colorMode).css('hsl')
19 | color = helpers.roundHslAlpha(color, alphaDecimals)
20 | if (Number(coordinate.x) !== 0 && Number(coordinate.x) !== 1) {
21 | colorStops.push(`${color} ${+percent.toFixed(2)}%`)
22 | } else {
23 | colorStops.push(color)
24 | }
25 | })
26 | return colorStops
27 | }
28 |
--------------------------------------------------------------------------------
/lib/helpers.js:
--------------------------------------------------------------------------------
1 | const chroma = require('chroma-js')
2 |
3 | // https://developer.mozilla.org/docs/Web/CSS/single-transition-timing-function
4 | const stepsFunction = 'steps'
5 | const cubicFunction = 'cubic-bezier'
6 | const easeShorthands = ['ease', 'ease-in', 'ease-out', 'ease-in-out']
7 | const timingFunctions = [...easeShorthands, cubicFunction, stepsFunction]
8 |
9 | /**
10 | * Test if a string has a parenthesis
11 | * @param {String} str A text string
12 | * @returns {Boolean} If a string has a "("
13 | */
14 | const hasParenthesis = str => str.indexOf('(') !== -1
15 |
16 | /**
17 | * Get substring before first parenthesis in a string
18 | * @param {String} str A text string
19 | * @returns {String} The substring if the provided string has a parenthesis. Otherwise the string.
20 | */
21 | const getBeforeParenthesisMaybe = str =>
22 | hasParenthesis(str) ? str.substring(0, str.indexOf('(')) : str
23 |
24 | /**
25 | * Test if a string matches a css timing function
26 | * @param {String} str A text string
27 | * @returns {Boolean} If the string is a timing function
28 | */
29 | exports.isTimingFunction = str => timingFunctions.includes(getBeforeParenthesisMaybe(str))
30 |
31 | /**
32 | * Get insides of a parenthesis
33 | * @param {String} str A text string
34 | * @returns {String} The substring within the parenthesis
35 | * Note: It's also used in this file so declared first and exported after
36 | */
37 | const getParenthesisInsides = str => str.match(/\((.*)\)/).pop()
38 | exports.getParenthesisInsides = getParenthesisInsides
39 |
40 | /**
41 | * If a color is transparent then convert it to a hsl-transparent of the paired color
42 | * @param {Array} colors An array with two colors
43 | * @returns {Object} A color as chroma object
44 | */
45 | exports.transparentFix = colors =>
46 | colors.map((color, i) => {
47 | return color === 'transparent'
48 | ? chroma(colors[Math.abs(i - 1)])
49 | .alpha(0)
50 | .css('hsl')
51 | : color
52 | })
53 |
54 | /**
55 | * Change value to ';' on child objects of type 'div'
56 | * @param {Array.