You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

400 lines
17 KiB

require! "./presets": {presets}
simple-str = (arr) -> arr.join ''
wrap = (content) -> "data:image/svg+xml;base64," + btoa(content)
do ->
make =
head: (viewBox) -> """
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="#viewBox">
"""
gradient: (dir = 45, dur = 1, ...colors) ->
ret = [@head "0 0 100 100"]
len = colors.length * 4 + 1
dir = dir * Math.PI / 180
gx = Math.cos(dir) ** 2
gy = Math.sqrt(gx - gx ** 2)
if dir > Math.PI * 0.25 =>
gy = Math.sin(dir) ** 2
gx = Math.sqrt(gy - gy ** 2)
x = gx * 100
y = gy * 100
ret.push """<defs><linearGradient id="gradient" x1="0" x2="#gx" y1="0" y2="#gy">"""
for i from 0 til len =>
idx = i * 100 / (len - 1)
ret.push """<stop offset="#{idx}%" stop-color="#{colors[i % colors.length]}"/>"""
ret.push """
</linearGradient></defs>
<rect x="0" y="0" width="400" height="400" fill="url(\#gradient)">
<animateTransform attributeName="transform" type="translate" from="-#x,-#y"
to="0,0" dur="#{dur}s" repeatCount="indefinite"/></rect></svg>
"""
wrap ret.join("")
stripe: (c1=\#b4b4b4, c2=\#e6e6e6, dur = 1) ->
ret = [@head "0 0 100 100"]
ret ++= [
"""<rect fill="#c2" width="100" height="100"/>"""
"""<g><g>"""
["""<polygon fill="#c1" """ +
"""points="#{-90 + i * 20},100 #{-100 + i * 20},""" +
"""100 #{-60 + i * 20},0 #{-50 + i * 20},0 "/>""" for i from 0 til 13].join("")
"""</g><animateTransform attributeName="transform" type="translate" """
"""from="0,0" to="20,0" dur="#{dur}s" repeatCount="indefinite"/></g></svg>"""
].join("")
wrap ret
bubble: (c1 = \#39d, c2 = \#9cf, count = 15, dur = 1, size = 6, sw=1) ->
ret = [@head("0 0 200 200"), """<rect x="0" y="0" width="200" height="200" fill="#c1"/>"""]
for i from 0 til count =>
idx = -(i / count) * dur
x = Math.random! * 184 + 8
r = ( Math.random! * 0.7 + 0.3 ) * size
d = dur * (1 + Math.random! * 0.5)
ret.push [
"""<circle cx="#x" cy="0" r="#r" fill="none" stroke="#c2" stroke-width="#sw">"""
"""<animate attributeName="cy" values="190;-10" times="0;1" """
"""dur="#{d}s" begin="#{idx}s" repeatCount="indefinite"/>"""
"""</circle>"""
"""<circle cx="#x" cy="0" r="#r" fill="none" stroke="#c2" stroke-width="#sw">"""
"""<animate attributeName="cy" values="390;190" times="0;1" """
"""dur="#{d}s" begin="#{idx}s" repeatCount="indefinite"/>"""
"""</circle>"""
].join("")
wrap(ret.join("") + "</svg>")
handler =
queue: {}
running: false
main: (timestamp) ->
keepon = false
removed = []
for k,func of @queue =>
ret = func timestamp
if !ret => removed.push func
keepon = keepon or ret
for k,func of @queue => if removed.indexOf(func) >= 0 => delete @queue[k]
if keepon => requestAnimationFrame (~> @main it)
else @running = false
add: (key, f) ->
if !@queue[key] => @queue[key] = f
if !@running =>
@running = true
requestAnimationFrame (~> @main it)
window.ldBar = ldBar = (selector, option = {}) ->
xmlns = xlink: "http://www.w3.org/1999/xlink"
root = if typeof! selector is \String
document.querySelector selector
else
selector
if !root.ldBar => root.ldBar = @
else return root.ldBar
cls = root.getAttribute(\class) or ''
if !~cls.indexOf('ldBar') => root.setAttribute \class, "#cls ldBar"
id-prefix = "ldBar-#{Math.random!toString 16 .substring 2}"
id =
key: id-prefix
clip: "#{id-prefix}-clip"
filter: "#{id-prefix}-filter"
pattern: "#{id-prefix}-pattern"
mask: "#{id-prefix}-mask"
mask-path: "#{id-prefix}-mask-path"
domTree = (n,o) ->
n = newNode n
for k,v of o => if k != \attr => n.appendChild domTree(k, v or {})
n.attrs(o.attr or {})
n
newNode = (n) -> document.createElementNS "http://www.w3.org/2000/svg", n
document.body.__proto__.__proto__.__proto__
..text = (t) -> @appendChild document.createTextNode(t)
..attrs = (o) -> for k,v of o =>
ret = /([^:]+):([^:]+)/.exec(k)
if !ret or !xmlns[ret.1] => @setAttribute k, v
else @setAttributeNS xmlns[ret.1], k, v
..styles = (o) -> for k,v of o => @style[k] = v
..append = (n) -> @appendChild r = document.createElementNS "http://www.w3.og/2000/svg", n
..attr = (n,v) -> if v? => @setAttribute n, v else @getAttribute n
config =
"type": 'stroke'
"img": ''
"path": 'M10 10L90 10M90 8M90 12'
"fill-dir": \btt
"fill": \#25b
"fill-background": \#ddd
"fill-background-extrude": 3
"pattern-size": null
"stroke-dir": \normal
"stroke": \#25b
"stroke-width": \3
"stroke-trail": \#ddd
"stroke-trail-width": 0.5
"duration": 1
"easing": \linear
"value": 0
"img-size": null
"bbox": null
"set-dim": true
"aspect-ratio": "xMidYMid"
"transition-in": false
"min": 0
"max": 100
"precision": 0
"padding": undefined
config["preset"] = root.attr("data-preset") or option["preset"]
if config.preset?
# use the default preset
config <<< presets[config.preset]
# overwrite if there are arguments passed via data-* attributes
for attr of config
if that = root.attr "data-#{attr}"
config[attr] = that
config <<< option
if config.img => config.path = null
is-stroke = config.type == \stroke
parse-res = (v) ->
parser = /data:ldbar\/res,([^()]+)\(([^)]+)\)/
ret = parser.exec(v)
if !ret => return v
ret = make[ret.1].apply make, ret.2.split(\,)
config.fill = parse-res config.fill
config.stroke = parse-res config.stroke
if config["set-dim"] == \false => config["set-dim"] = false
dom =
attr:
"xmlns:xlink": \http://www.w3.org/1999/xlink
preserveAspectRatio: config["aspect-ratio"]
width: "100%", height: "100%"
defs:
filter:
attr: id: id.filter, x: -1, y: -1, width: 3, height: 3
feMorphology: attr:
operator: (if +config["fill-background-extrude"]>=0 => \dilate else \erode)
radius: Math.abs(+config["fill-background-extrude"])
feColorMatrix: attr: {values: '0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0', result: "cm"}
mask:
attr: id: id.mask
image: attr:
"xlink:href": config.img
filter: "url(\##{id.filter})"
x: 0, y: 0, width: 100, height: 100, preserveAspectRatio: config["aspect-ratio"]
g:
mask:
attr: id: id.mask-path
path: attr:
d: config.path or ""
fill: \#fff
stroke: \#fff
filter: "url(\##{id.filter})"
clipPath:
attr: id: id.clip
rect: {attr: class: \mask, fill: \#000}
pattern:
attr:
id: id.pattern, patternUnits: \userSpaceOnUse
x:0, y: 0, width: 300, height: 300
image: attr: x: 0, y: 0, width: 300, height: 300
svg = domTree \svg, dom
text = document.createElement \div
text.setAttribute \class, \ldBar-label
root.appendChild svg
root.appendChild text
group = [0,0]
length = 0
@fit = ~>
if config["bbox"] =>
box = that.split(' ').map(->+(it.trim!))
box = {x: box.0, y: box.1, width: box.2, height: box.3}
else box = group.1.getBBox!
if !box or box.width == 0 or box.height == 0 => box = {x: 0, y: 0, width: 100, height: 100}
d = (Math.max.apply(
null, <[stroke-width stroke-trail-width fill-background-extrude]>.map(->config[it]))
) * 1.5
if config["padding"]? => d = +config["padding"]
svg.attrs viewBox: [box.x - d, box.y - d, box.width + d * 2, box.height + d * 2].join(" ")
if config["set-dim"] => <[width height]>.map ~> if !root.style[it] or @fit[it] =>
root.style[it] = "#{box[it] + d * 2}px"
@fit[it] = true
rect = group.0.querySelector \rect
if rect => rect.attrs do
x: box.x - d, y: box.y - d, width: box.width + d * 2, height: box.height + d * 2
if config.path =>
if is-stroke =>
group.0 = domTree \g, path: attr:
d: config.path
fill: \none
class: \baseline
else
group.0 = domTree \g, rect: attr:
x: 0, y: 0, width: 100, height: 100
mask: "url(\##{id.mask-path})", fill: config["fill-background"]
class: \frame
svg.appendChild group.0
group.1 = domTree \g, path: attr:
d: config.path, class: if is-stroke => \mainline else \solid
"clip-path": if config.type == \fill => "url(\##{id.clip})" else ''
svg.appendChild group.1
path0 = group.0.querySelector (if is-stroke => \path else \rect)
path1 = group.1.querySelector \path
if is-stroke => path1.attrs fill: \none
patimg = svg.querySelector 'pattern image'
img = new Image!
img.addEventListener \load, ->
box = if config["pattern-size"] => {width: +that, height: +that}
else if img.width and img.height => {width: img.width, height: img.height}
else {width: 300, height: 300}
svg.querySelector \pattern .attrs {width: box.width, height: box.height}
patimg.attrs {width: box.width, height: box.height}
if /.+\..+|^data:/.exec(if !is-stroke => config.fill else config.stroke) =>
img.src = if !is-stroke => config.fill else config.stroke
patimg.attrs "xlink:href": img.src #if !is-stroke => config.fill else config.stroke
if is-stroke =>
path0.attrs stroke: config["stroke-trail"], "stroke-width": config["stroke-trail-width"]
path1.attrs do
"stroke-width": config["stroke-width"]
stroke: if /.+\..+|^data:/.exec(config.stroke) => "url(\##{id.pattern})" else config.stroke
if config.fill and !is-stroke =>
path1.attrs do
fill: if /.+\..+|^data:/.exec(config.fill) => "url(\##{id.pattern})" else config.fill
length = path1.getTotalLength!
@fit!
@inited = true
else if config.img =>
if config["img-size"] =>
ret = config["img-size"].split(\,)
size = {width: +ret.0, height: +ret.1}
else size = {width: 100, height: 100}
group.0 = domTree \g, rect: attr:
x: 0, y: 0, width: 100, height: 100, mask: "url(\##{id.mask})", fill: config["fill-background"]
svg.querySelector 'mask image' .attrs do
width: size.width, height: size.height
group.1 = domTree \g, image: attr:
width: size.width, height: size.height, x: 0, y: 0, preserveAspectRatio: config["aspect-ratio"]
#width: 100, height: 100, x: 0, y: 0, preserveAspectRatio: "xMidYMid"
"clip-path": if config.type == \fill => "url(\##{id.clip})" else ''
"xlink:href": config.img, class: \solid
img = new Image!
img.addEventListener \load, ~>
if config["img-size"] =>
ret = config["img-size"].split(\,)
size = {width: +ret.0, height: +ret.1}
else if img.width and img.height => size = {width: img.width, height: img.height}
else size = {width: 100, height: 100}
svg.querySelector 'mask image' .attrs do
width: size.width, height: size.height
group.1.querySelector 'image' .attrs do
width: size.width, height: size.height
@fit!
# image is load, so we set value again.
# if we need transition - we have to clean value so it will be treated as 0.
v = @value
@value = undefined
@set v, true
@inited = true
img.src = config.img
svg.appendChild group.0
svg.appendChild group.1
svg.attrs width: \100%, height: \100% #, viewBox: '0 0 100 100'
@transition =
value:
src: 0
des: 0
time: {}
ease: (t,b,c,d) ->
t = t / (d * 0.5)
if t < 1 => return c * 0.5 * t * t + b
t = t - 1
return -c * 0.5 * (t*(t - 2) - 1) + b
handler: (time, doTransition = true) ->
if !@time.src? => @time.src = time
[min,max,prec] = [config["min"], config["max"],1/config["precision"]]
[dv, dt, dur] = [@value.des - @value.src, (time - @time.src) * 0.001, +config["duration"] or 1]
v = if doTransition => @ease(dt, @value.src, dv, dur) else @value.des
if config.precision => v = Math.round(v * prec) / prec
else if doTransition => v = Math.round(v)
v >?= min
v <?= max
text.textContent = v
p = 100.0 * (v - min ) / ( max - min )
if is-stroke =>
node = path1
style =
"stroke-dasharray": (
if config["stroke-dir"] == \reverse =>
"0 #{length * (100 - p) * 0.01} #{length * p * 0.01} 0"
else => "#{p * 0.01 * length} #{(100 - p) * 0.01 * length + 1}"
)
else
box = group.1.getBBox!
dir = config["fill-dir"]
style = if dir == \btt or !dir => do
y: box.y + box.height * (100 - p) * 0.01
height: box.height * p * 0.01
x: box.x, width: box.width
else if dir == \ttb => do
y: box.y, height: box.height * p * 0.01
x: box.x, width: box.width
else if dir == \ltr => do
y: box.y, height: box.height
x: box.x, width: box.width * p * 0.01
else if dir == \rtl => do
y: box.y, height: box.height
x: box.x + box.width * (100 - p) * 0.01
width: box.width * p * 0.01
node = svg.querySelector \rect
node.attrs style
if dt >= dur => delete @time.src; return false
return true
start: (src, des, doTransition) ->
@value <<< {src, des}
!!( root.offsetWidth || root.offsetHeight || root.getClientRects!length )
if !doTransition or !( root.offsetWidth || root.offsetHeight || root.getClientRects!length ) =>
@time.src = 0
@handler 1000, false
return
handler.add id.key, (time) ~> return @handler time
@set = (v,doTransition = true) ->
src = @value or 0
if v? => @value = v else v = @value
des = @value
@transition.start src, des, doTransition
@set (+config.value or 0), config["transition-in"] or false
@
window.addEventListener \load, (->
for node in document.querySelectorAll(\.ldBar) =>
if !node.ldBar => node.ldBar = new ldBar node
), false
module.exports = ldBar