Runtime CSS manager, Turn CSS into dynamic JS module, Stylesheet CRUD (Create, Read, Update, Delete) in CSSOM, Solve common problems of CSS-in-JS.
- ~4K min.gz, simple API
- Nested rules, support any CSS selector/value
- Minimal work to migrate
- Work with DOM Frameworks
- CSS Rules CRUD
- Put class names into local space No Conflict
- Use JS function as CSS value
- Conditional Apply CSS
- Server Rendering
Usage - Wiki - API - Demo - React - Babel
Install:
npm
npm install cssobj # the lib
# When use Babel
npm install babel-plugin-transform-cssobj
# When **NOT** use Babel, install the converter
npm install -g cssobj-converter
browser
<script src="https://unpkg.com/cssobj"></script>
Usage
First see this SIMPLE DEMO
In the example, cssobj
will create <style>
tag in HEAD, render CSS rules inside
import cssobj from 'cssobj'
const obj = {
div: {
backgroundColor: 'yellow',
color: 'red',
// simulate 50vh in CSS3
height: () => window.innerHeight/2 + 'px'
}
}
const result = cssobj(obj)
// dynamic update height when resize
window.onresize = () => result.update()
The rendered CSS (height
is dynamically set to 50% of window height)
div { background-color: yellow; color: red; height: 600px; }
If you read the code, you've learned the API already:
Only One top level method: cssobj( obj, [config] )
, all other things using result.someMethods
, that's all, really.
Stylesheet CRUD
The power of cssobj is CSS CRUD (Create, Read, Update, Delete), dynamically change above CSS, see below:
1. Update property values
You want to change color to 'blue'
// using static value:
obj.div.color = 'blue'
result.update() // color is now 'blue'
// using function as value:
obj.div.color = function(v){
return randomColor()
}
result.update() // color is now random
2. Delete/Remove properties
You want to remove backgroundColor
It's just work as you expected:
delete obj.div.backgroundColor
result.update()
3. Create/Add new properties
You want to add 'float'
and 'clear'
It's just work as you expected:
obj.div.float = 'left'
obj.div.clear = 'both'
result.update()
4. Create/Add new rules
You want to add ':after'
rule, and div span
rule
obj.div['&:after'] = { fontSize:'10px', content:'"---"' }
obj.div.span = { fontSize: '18px' }
result.update()
5. Update/Replace rules
You want to replace the whole rule
obj.div.span = { color: 'green', fontSize: '20px' }
result.update()
All the above can use function
instead
obj.div.span = function() {
return { color: randomColor(), fontSize: currentSize + 'px' }
}
result.update()
6. Delete/Remove rules
You want to remove div span
rule
delete obj.div.span
result.update()
7. Read a rule
Although cssobj
can manage everything, you read the rule in stylesheet manually
const rule = result.root.children.div.omRule[0]
// => CSSStyleRule
rule.color = 'red'
8. Delete/Destroy cssobj
Currently, cssobj
don't provide result.destroy()
or similar method, you should manually destroy things:
// remove <style> tag
result.cssdom.parentNode.removeChild(el)
// GC result
result = null
Think of this: one cssobj
instance === A <style>
tag with rules + A manager from JS
At-Rules
All @-rules
work as expected, and @media
can be nested at any level:
cssobj({
'.nav':{
width: '1024px',
'@media print': {
display: 'none'
}
}
})
Above will hide .nav
when print.
You can emit any @media
rule by cssom.media
option:
const result = cssobj({
'.nav':{
width: '1024px',
'@media print': {
color: 'red'
}
}
}, { cssom: { media:'' } })
result.config.cssom.media = 'print'
result.update()
Above will switch to print
view, with below CSS:
nav {width: 1024px;}
nav {color: red;}
Then switch back:
result.config.cssom.media = ''
result.update()
cssobj({
'@keyframes changeColor': {
'0%': { backgroundColor: 'green' },
'100%': { backgroundColor: 'yellow' }
},
'.nav': {
backgroundColor: 'red',
animation: '5s infinite changeColor'
}
})
Notice above @keyframes
, it have to be in top level of your source object, aka cannot be nested into .nav
,
that is different from @media
rule, which allow nested at any level, or nested into another @media
:
cssobj({
h3:{
color: 'blue',
'@media (min-width: 400px)': {
color: 'red',
'@media (max-width: 500px)': {
color: 'green'
}
},
'@media (min-width: 500px)': {
color: 'purple'
}
}
})
Above, what's the color will be? You can take a try and see what's the final CSS will be.
There's a hidden JS Bin...
Localize class names
Passing local: true
as option, cssobj will add a random name space
into all class names, this is called localize
:
const result = cssobj(
{
'.nav': {color: 'red'}
},
{ local: true }
)
Rendered CSS:
.nav_1lwyllh4_ {color: red;}
You can get this name space
using result.space
, or using below methods:
// As HTML class attribute
result.mapClass('nav active') // [string] 'nav_1lwyllh4_ active_1lwyllh4_'
// As CSS selector
result.mapSel('.nav li.item') // [string] '.nav_1lwyllh4_ li.item_1lwyllh4_'
React
You can use react-cssobj with React, like below:
import React from 'react'
import ReactCSS from 'react-cssobj'
const {css, mapClass} = ReactCSS({
'.app': {
background: 'red'
}
})
export default class App extends React.Component {
render(){
return mapClass (<div className = 'app'>App</div>)
}
}
Without Babel Version
Work Flow with Babel, See alsoIf use Babel, recommended the babel-plugin-transform-cssobj
// create <style> in <head>, insert CSS rules, random namespace: _1jkhrb92_
// The babel-plugin only transform: CSSOBJ `text`
const result = CSSOBJ `
---
# cssobj config
local: true
plugins:
- default-unit: px
---
// SCSS style (nested)
.nav {
color: blue;
height: 100;
// font-size is a function
.item { color: red; font-size: ${v => v.raw ? v.raw + 1 : 12} }
// nested @media
@media (max-width: 800px) {
color: #333;
// & = parent selector = .nav
&:active {
color: #666;
}
}
}
`
const html = result.mapClass(<ul class='nav'><li class='item active'>ITEM</li></ul>)
// <ul class="nav_1jkhrb92_"><li class="item_1jkhrb92_ active_1jkhrb92_"></li></ul>
Rendered result as below:
import cssobj from "cssobj";
import cssobj_plugin_default_unit from "cssobj-plugin-default-unit";
const result = cssobj({
'.nav': {
color: 'blue',
height: 100,
'.item': {
color: 'red',
fontSize: v => v.raw ? v.raw + 1 : 12
},
'@media (max-width: 800px)': {
color: '#333',
'&:active': {
color: '#666'
}
}
}
}, {
local: true,
plugins: [cssobj_plugin_default_unit('px')]
});
const html = <ul class={result.mapClass('nav')}><li class={result.mapClass('item active')}></li></ul>
For this first time render,
all class names add a random suffix _1jkhrb92_
,
the font-size
is 12px
,
the <style>
tag which cssobj
created now contains:
.nav_1jkhrb92_ { color: blue; height: 100px; }
.nav_1jkhrb92_ .item_1jkhrb92_ { color: red; font-size: 12px; }
@media (max-width: 800px) {
.nav_1jkhrb92_ { color: rgb(51, 51, 51); }
.nav_1jkhrb92_:active { color: rgb(102, 102, 102); }
}
Update CSS Value
Since we already have a function as the value:
fontSize: v => v.raw ? v.raw + 1 : 12
-
the value (===
v.raw
) initialised with12
(default-unit
plugin will addpx
when rendering, that isv.cooked
===12px
) -
each call of the function will increase
font-size
by 1
So, just need call result.update
, the function invoked, stylesheet updated, automatically:
result.update()
// font-size -> 13px
result.update()
// font-size -> 14px
Above, only font-size
changed, all other things keep untouched
CRUD (Create, Read, Update, Delete) stylesheet from JS
When the source JS Object (first arg of cssobj()
) have no changes,
result.update
only invoke the value function (here, the above font-size
function),
Otherwise, it will look into the source JS Object, find which part have been changed (diff), and update stylesheet accordingly. See below:
// result.obj === reference of the source js object
// change a css property
result.obj['.nav'].color = 'orange'
// remove a css property
delete result.obj['.nav'].height
// add a new css property
result.obj['.nav'].width = 200
// add a new rule
result.obj['.nav'].a = { color: 'blue', '&:hover': {textDecoration: 'none'} }
// delete a rule
delete result.obj['.nav']['.item']
result.update()
// color -> 'orange' (PROP CHANGED)
// height -> (PROP REMOVED)
// width -> 200px (PROP ADDED)
// a, a:hover -> (RULE ADDED)
// .item -> (RULE REMOVED)
Above, only diffed part updated, other rules and props will keep untouched
Now, the stylesheet becomes:
.nav_1jkhrb92_ { color: orange; width: 200px; }
@media (max-width: 800px) {
.nav_1jkhrb92_ { color: #333; }
.nav_1jkhrb92_:active { color: #666; }
}
.nav_1jkhrb92_ a { color: blue; }
.nav_1jkhrb92_ a:hover { text-decoration: none; }
Diff with NEW JS Object
const newObj = { '.nav': { width: 100, a: { color: 'blue' } } }
result.update(newObj)
// cssobj will DIFF with old obj, keep same part, change diffed part in stylesheet!
// .nav, .nav a rules keeped
// width -> 100px, drop all other rules/props
Now, the stylesheet becomes:
/* below 2 rules keeped */
.nav_1jkhrb92_ { width: 100px; }
.nav_1jkhrb92_ a { color: blue; }
/* other rules gone */
That's it, see more Usage & Example
Work Flow (Without Babel)
First install cssobj-converter
npm install -g cssobj-converter
- Step 1
Write your CSS as normal (e.g. index.css)
// file: index.css
.nav { color: blue; font-size: 12px; }
- Step 2
Turn it into JS module, from cssobj-converter
CLI
# in command line, run cssobj-converter
cssobj index.css -o index.css.js
The result
// file: index.css.js
module.exports = {
'.nav': { color: 'blue', fontSize: '12px' }
}
- Step 3
Let's rock:
// import your css module
const obj = require('./index.css')
// create <style> tag in <head>, with rules in obj.
// `local: true` will put class names into local space
const result = cssobj(obj, {local: true})
result.mapClass(<JSX>) // with Babel
result.mapClass('classA') // without Babel
// update some rule
obj['.nav'].color = 'red'
obj['.nav'].fontSize = v => parseInt(v.cooked) + 1 // increase font-size by 1
result.update()
Documented API
More to read:
-
!important CSSOBJ Format
How it worked?
-
cssobj first parse js object into Virtual CSSOM middle format.
-
The internal cssom plugin will create stylesheet dom, and apply rules from middle format.
-
When the js object changed, cssobj will diff CSSOM rules (add/delete/change) accordingly. (see demo)
Tools
Convert existing style sheet into cssobj:
-
CLI Converter Recommended CLI tools to convert CSS. Run
npm -g cssobj-converter
-
Online Converter It's free node server, slow, and unstalbe, not recommended
Debug
- cssobj-helper-showcss Display css string from style tag, for DEBUG
Plugins
About writing a plugin, See: plugin-guide
-
(already in core) cssobj-plugin-localize Localize class names
-
(already in core) cssobj-plugin-cssdom Inject style to DOM and diff update
-
cssobj-plugin-default-unit Add default unit to numeric values, e.g. width / height
-
cssobj-plugin-flexbox Make flexbox working right with auto prefixer/transform
-
cssobj-plugin-replace Merge cssobj Key/Value with new object
-
cssobj-plugin-extend Extend to another selector, like @extend in SCSS or :extend in LESS
-
cssobj-plugin-keyframes Make keyframe names localized, and apply to
animation
andanimation-name
-
cssobj-plugin-gencss Generate css text from Virtual CSS Node, for Server Rendering
Helpers
-
babel-plugin-transform-cssobj Work with React, Vue etc. that can use babel+jsx
-
cssobj-mithril Help cssobj to work with mithril
-
cssobj-helper-stylize Add css string into style dom
Demos
Test
Using phantom 2.0 to test with CSSOM. Please see test/ folder.
Remark
cssobj is wrapper for cssobj-core, plugin-localize and plugin-cssom.
License
MIT