Added Animate Order extension

This commit is contained in:
Mario Voigt 2021-04-21 18:02:20 +02:00
parent 8fcc0cbf64
commit 9ca42fc792
38 changed files with 23812 additions and 0 deletions

View File

@ -1,2 +1,4 @@
eclipse.preferences.version=1
encoding//extensions/fablabchemnitz/create_links.py=utf-8
encoding//extensions/fablabchemnitz/next_gen.py=utf-8
encoding//extensions/fablabchemnitz/svgo-inkscape/svgo.inkscape.py=utf-8

View File

@ -0,0 +1,18 @@
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animate Order - Kopfteil-Laser.svg</title>
<meta name="description" content="SVG Drawing Animation">
</head>
<body>
<button onclick="vi.reset().play();">replay</button>
<br/>
<object id="animate_order" type="image/svg+xml" data="drawing.svg"></object>
<script src="./vivus-0.4.6/dist/vivus.js"></script>
<script>
var vi = new Vivus('animate_order', {type: 'oneByOne', duration:7200.0, reverseStack:false});
</script>
</body>
</html>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Animate Order</name>
<id>fablabchemnitz.de.animate_order</id>
<param name="tab" type="notebook">
<page name="tab_general" gui-text="Animate Order">
<label appearance="header">Warning!</label>
<label>Do not use "-inkscape-stroke:hairline" attribute for your paths. It will not render. Your page might be blank!</label>
<spacer/>
<param name="time" type="float" min="0.000" max="9999999.999" precision="3" gui-text="Duration (seconds)">5.0</param>
<param name="fps" type="int" min="1" max="100" gui-text="Frame per second (fps)">60</param>
<param name="sequence_type" type="optiongroup" appearance="combo" gui-text="Sequence type">
<option value="oneByOne">Line by line (one by one)</option>
<option value="delayed">Delayed</option>
</param>
<param name="reverse" type="bool" gui-text="Reverse order">false</param>
<param name="browser" type="optiongroup" appearance="combo" gui-text="Browser" gui-description="Select your desired browser (must be installed and must exist in %PATH% variable).">
<option value="chromium">Chromium</option>
<option value="chrome">Chrome</option>
<option value="firefox">Firefox</option>
</param>
</page>
<page name="tab_about" gui-text="About">
<label appearance="header">Animate Order</label>
<label>Create SVG preview file and show it in browser. Helps to quickly evaluate line order for cutting processes.</label>
<label>2021 / written by Mario Voigt (Stadtfabrikanten e.V. / FabLab Chemnitz)</label>
<spacer/>
<label appearance="header">Online Documentation</label>
<label appearance="url">https://y.stadtfabrikanten.org/animateorder</label>
<spacer/>
<label appearance="header">Contributing</label>
<label appearance="url">https://gitea.fablabchemnitz.de/MarioVoigt/mightyscape-1.X</label>
<label appearance="url">mailto:mario.voigt@stadtfabrikanten.org</label>
<spacer/>
<label appearance="header">Third Party Modules</label>
<label appearance="url">https://github.com/maxwellito/vivus</label>
<spacer/>
<label appearance="header">MightyScape Extension Collection</label>
<label>This piece of software is part of the MightyScape for Inkscape Extension Collection and is licensed under GNU GPL v3</label>
<label appearance="url">https://y.stadtfabrikanten.org/mightyscape-overview</label>
</page>
<page name="tab_donate" gui-text="Donate">
<label appearance="header">Coffee + Pizza</label>
<label>We are the Stadtfabrikanten, running the FabLab Chemnitz since 2016. A FabLab is an open workshop that gives people access to machines and digital tools like 3D printers, laser cutters and CNC milling machines.</label>
<spacer/>
<label>You like our work and want to support us? You can donate to our non-profit organization by different ways:</label>
<label appearance="url">https://y.stadtfabrikanten.org/donate</label>
<spacer/>
<label>Thanks for using our extension and helping us!</label>
<image>../000_about_fablabchemnitz.svg</image>
</page>
</param>
<effect needs-live-preview="true">
<object-type>all</object-type>
<effects-menu>
<submenu name="FabLab Chemnitz">
<submenu name="Nesting/Cut Optimization"/>
</submenu>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">animate_order.py</command>
</script>
</inkscape-extension>

View File

@ -0,0 +1,88 @@
#!/usr/bin/env python3
import inkex
import subprocess
import shutil
import os
import sys
"""
Extension for InkScape 1.X
Features
- Create SVG preview file and show it in browser. Helps to quickly evaluate line order for cutting processes
Author: Mario Voigt / FabLab Chemnitz
Mail: mario.voigt@stadtfabrikanten.org
Date: 21.04.2021
Last patch: 21.04.2021
License: GNU GPL v3
Used version of Vivus JS library: https://github.com/maxwellito/vivus/releases/tag/v0.4.6 - MIT License
Browser config:
Firefox via about:config -> privacy.file_unique_origin = false
ToDo:
- adjust width and height (give options)
- embed config buttons inside html to adjust time/type/... (more flexible than clicking from Inkscape)
- calculate the total length of all paths and auto-adjust the speed to have good visibility
"""
class AnimateOrder (inkex.EffectExtension):
def add_arguments(self, pars):
pars.add_argument("--tab")
pars.add_argument("--time", type=float, default = 5.0, help="Duration (seconds)")
pars.add_argument("--fps", type=int, default = 60.0, help="Frames per second (fps)")
pars.add_argument("--sequence_type", help="Sequence type")
pars.add_argument("--reverse", type = inkex.Boolean, default = False, help="Reverse order")
pars.add_argument("--browser", help="Select your desired browser (must be installed and must exist in %PATH% variable).")
def effect(self):
#write current SVG to extensions' directory. Target name must be "drawing.svg" because it is embedded in animate_order.html statically
inFile = "drawing.svg"
extension_dir = os.path.dirname(os.path.realpath(__file__))
shutil.copy2(self.options.input_file, os.path.join(extension_dir, inFile))
target_html = os.path.join(extension_dir, "animate_order.html")
title = "Animate Order - " + self.document.getroot().get("{http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd}docname")
if title is None:
title = "Animate Order - Vivus JS"
vivus_include = "./vivus-0.4.6/dist/vivus.js"
duration = self.options.time * self.options.fps # we guess we have 20 ... 60 fps. depends on performance of the machine
frames_per_second = self.options.fps
type = self.options.sequence_type
reverse = str(self.options.reverse).lower()
with open(target_html, "w") as text_file:
print( '<html>' , file=text_file)
print( ' <head>' , file=text_file)
print( ' <meta charset="UTF-8">' , file=text_file)
print( ' <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">' , file=text_file)
print( ' <meta name="viewport" content="width=device-width, initial-scale=1.0">' , file=text_file)
print(f' <title>{title}</title>' , file=text_file)
print( ' <meta name="description" content="SVG Drawing Animation">' , file=text_file)
print( ' </head>' , file=text_file)
print( ' <body>' , file=text_file)
print( ' <button onclick="vi.reset().play();">replay</button>' , file=text_file)
print( ' <br/>' , file=text_file)
print(f' <object id="animate_order" type="image/svg+xml" data="{inFile}"></object>' , file=text_file)
print(f' <script src="{vivus_include}"></script>' , file=text_file)
print( ' <script>' , file=text_file)
print( " var vi = new Vivus('animate_order', {type: '" + f'{type}' + "', \
duration:" + f'{duration}' + ", reverseStack:" + f'{reverse}' + "});" , file=text_file)
print( ' </script>' , file=text_file)
print( ' </body>' , file=text_file)
print( '</html>' , file=text_file)
#now open firefox
args = [self.options.browser, target_html]
proc = subprocess.Popen(args, shell=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
if proc.returncode != 0:
inkex.utils.debug("%d %s %s" % (proc.returncode, stdout, stderr))
if __name__ == '__main__':
AnimateOrder().run()

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 504 KiB

View File

@ -0,0 +1,37 @@
{
"env": {
"es6": false,
"amd": false,
"browser": true,
"jasmine": true,
"node": true
},
"extends": "eslint:recommended",
"parserOptions": {
"sourceType": "module"
},
"globals": {
"Pathformer": true,
"Vivus": true,
"window": true,
"document": true,
"define": true,
"jasmine": true,
"it": true,
"expect": true,
"describe": true,
"beforeEach": true,
"afterEach": true,
"spyOn": true
},
"rules": {
"no-cond-assign": 2,
"no-console": 0,
"no-const-assign": 2,
"no-class-assign": 2,
"no-this-before-super": 2,
"no-unused-vars": 1,
"no-empty": 0,
"object-shorthand": [2, "always"]
}
}

View File

@ -0,0 +1,16 @@
### Please define your problem or issue as clear as possible.
For a problem, please fill the following:
**Vivus version**:
**Browser**:
**Steps to reproduce it**:
**JSFiddle link (or similar platform)**:
*No personal website will be allowed, only sandboxed platform where the code is isolated, clear and can be hacked. I don't want to debug uglified code between 42 libraries.*
*[note]*
Please have a minimum of politeness. There's unfortunately only me as contributor/maintainor which help on my free time, I'm not the Amazon customer service or your Mom. I don't ask to send me flowers and tell me how amazing Vivus is (that won't make me help you quicker). But please try to do as much as you can before opening an issue: check that no closed issue mention a similar problem, that your script is executed correctly (conflicts, race conditions...).. Thanks :)
*[/note]*

View File

@ -0,0 +1,6 @@
coverage/
node_modules/
.DS_Store
.idea
yarn.lock
dist/vivus.min.js.map

View File

@ -0,0 +1,5 @@
test
assets
node_modules
coverage
.DS_Store

View File

@ -0,0 +1,8 @@
language: node_js
node_js:
- 'stable'
script: npm run lint && npm run test
before_install:
- export CHROME_BIN=chromium-browser
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start

View File

@ -0,0 +1,7 @@
# Contributing
First of all, thanks for contributing, thinking about contributing, or even arriving here by mistake.
Issues are reserved to mention a problem, a bug or discuss about implementing a feature. If you have any question or support request, please use [Gitter](https://gitter.im/maxwellito/vivus). For every issue, please try to give as much information as you can : version, steps to recreate it, examples (via jsFiddle or something like that).
About pull requests, please try to contact the maintainer beforehand. He's a kinda human Grumpy Cat trying to avoid features which can be useful for only 1% of users. The warning is only because it would be sad to see contributors spending time a feature that wouldn't be merged. Otherwise, I would recommend you to add a section in the `hacks.md` file. But if it's a bug fix, the chances to be merged are higher. If necessary please think about updating the tests.

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) maxwellito
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,95 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 15.0.2, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="200px"
height="90px" viewBox="0 0 200 90" enable-background="new 0 0 200 90" xml:space="preserve">
<g id="Layout">
<g id="time">
<polyline fill="none" stroke="#000000" stroke-width="2" stroke-linecap="square" stroke-miterlimit="10" points="5,73 190,73
185.25,70 185.25,71.75 "/>
<text transform="matrix(1 0 0 1 88.6001 86.5)" font-family="'MyriadPro-Regular'" font-size="12">time</text>
</g>
</g>
<g id="Scenarios">
<g id="async" display="none">
<g id="timeline_3_" display="inline">
<path fill="#4D4D4D" d="M195,6c0,0.55-0.45,1-1,1H6C5.45,7,5,6.55,5,6V4c0-0.55,0.45-1,1-1h188c0.55,0,1,0.45,1,1V6z"/>
<path fill="#4D4D4D" d="M195,13c0,0.55-0.45,1-1,1H6c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h188c0.55,0,1,0.45,1,1V13z"/>
<path fill="#4D4D4D" d="M195,20c0,0.55-0.45,1-1,1H6c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h188c0.55,0,1,0.45,1,1V20z"/>
<path fill="#4D4D4D" d="M195,27c0,0.55-0.45,1-1,1H6c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h188c0.55,0,1,0.45,1,1V27z"/>
<path fill="#4D4D4D" d="M195,34c0,0.55-0.45,1-1,1H6c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h188c0.55,0,1,0.45,1,1V34z"/>
</g>
<g id="duration_3_" display="inline">
<rect x="5" y="52.75" fill="#D36281" width="190" height="4"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="5" y1="56.75" x2="5" y2="5"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="195" y1="56.75" x2="195" y2="33"/>
</g>
</g>
<g id="oneByOne">
<g id="timeline_2_">
<path fill="#4D4D4D" d="M43,6c0,0.55-0.45,1-1,1H6C5.45,7,5,6.55,5,6V4c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V6z"/>
<path fill="#4D4D4D" d="M81,13c0,0.55-0.45,1-1,1H44c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V13z"/>
<path fill="#4D4D4D" d="M119,20c0,0.55-0.45,1-1,1H82c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V20z"/>
<path fill="#4D4D4D" d="M157,27c0,0.55-0.45,1-1,1h-36c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V27z"/>
<path fill="#4D4D4D" d="M195,34c0,0.55-0.45,1-1,1h-36c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V34z"/>
</g>
<g id="duration_2_" display="none">
<rect x="43" y="52.75" display="inline" fill="#D36281" width="38" height="4"/>
<line display="inline" fill="none" stroke="#D36281" stroke-miterlimit="10" x1="43" y1="56.75" x2="43" y2="12"/>
<line display="inline" fill="none" stroke="#D36281" stroke-miterlimit="10" x1="81" y1="56.75" x2="81" y2="12"/>
</g>
</g>
<g id="delayed">
<g id="timeline_1_" display="none">
<path display="inline" fill="#4D4D4D" d="M132,6c0,0.55-0.45,1-1,1H6C5.45,7,5,6.55,5,6V4c0-0.55,0.45-1,1-1h125
c0.55,0,1,0.45,1,1V6z"/>
<path display="inline" fill="#4D4D4D" d="M147.75,13c0,0.55-0.45,1-1,1h-125c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h125
c0.55,0,1,0.45,1,1V13z"/>
<path display="inline" fill="#4D4D4D" d="M163.5,20c0,0.55-0.45,1-1,1h-125c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h125
c0.55,0,1,0.45,1,1V20z"/>
<path display="inline" fill="#4D4D4D" d="M179.25,27c0,0.55-0.45,1-1,1h-125c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h125
c0.55,0,1,0.45,1,1V27z"/>
<path display="inline" fill="#4D4D4D" d="M195,34c0,0.55-0.45,1-1,1H69c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h125
c0.55,0,1,0.45,1,1V34z"/>
</g>
<g id="duration">
<rect x="5" y="52.75" fill="#D36281" width="190" height="4"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="5" y1="56.75" x2="5" y2="5"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="195" y1="56.75" x2="195" y2="33"/>
</g>
<g id="delay" display="none">
<rect x="5" y="45.75" display="inline" fill="#5AA8C5" width="63" height="4"/>
<line display="inline" fill="none" stroke="#5AA8C5" stroke-miterlimit="10" x1="5" y1="49.75" x2="5" y2="5"/>
<line display="inline" fill="none" stroke="#5AA8C5" stroke-miterlimit="10" x1="68" y1="49.75" x2="68" y2="33"/>
</g>
</g>
<g id="custom" display="none">
<g id="timeline" display="inline">
<path fill="#4D4D4D" d="M24,6c0,0.55-0.45,1-1,1H6C5.45,7,5,6.55,5,6V4c0-0.55,0.45-1,1-1h17c0.55,0,1,0.45,1,1V6z"/>
<path fill="#4D4D4D" d="M79,13c0,0.55-0.45,1-1,1H42c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V13z"/>
<path fill="#4D4D4D" d="M88,20c0,0.55-0.45,1-1,1H51c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h36c0.55,0,1,0.45,1,1V20z"/>
<path fill="#4D4D4D" d="M195,27c0,0.55-0.45,1-1,1h-17c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h17c0.55,0,1,0.45,1,1V27z"/>
<path fill="#4D4D4D" d="M195,34c0,0.55-0.45,1-1,1h-93c-0.55,0-1-0.45-1-1v-2c0-0.55,0.45-1,1-1h93c0.55,0,1,0.45,1,1V34z"/>
</g>
<g id="duration_1_" display="inline">
<rect x="41" y="52.75" fill="#D36281" stroke="#D36281" stroke-miterlimit="10" width="38" height="4"/>
<g>
<g>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="41" y1="56.75" x2="41" y2="55.75"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" stroke-dasharray="2.0357,2.0357" x1="41" y1="53.714" x2="41" y2="14.018"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="41" y1="13" x2="41" y2="12"/>
</g>
</g>
<g>
<g>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="79" y1="56.75" x2="79" y2="55.75"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" stroke-dasharray="2.0357,2.0357" x1="79" y1="53.714" x2="79" y2="14.018"/>
<line fill="none" stroke="#D36281" stroke-miterlimit="10" x1="79" y1="13" x2="79" y2="12"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View File

@ -0,0 +1,16 @@
{
"name": "vivus",
"description": "JavaScript library to make drawing animation on SVG",
"main": "dist/vivus.js",
"licence": "MIT",
"ignore": [
"assets/",
"src/",
"test/",
".gitignore",
".jshintrc",
"gulpfile.js",
"index.html",
"package.json"
]
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,30 @@
# Vivus hacks
Some tricks about Vivus are very interesting and might help you but doesn't have their place in the documentation. So you will see them here.
## Animate `fill` with CSS
On inline SVG, it's very easy to animate fill color with just a bit of CSS.
Let's imagine you have an inline SVG element in your page with the ID `mySVG`. You apply the following CSS to make it with fill opacity to 0 by default, and a class with the opacity of 1 (with transition). Then once the animation finished, the class can be added to the svg.
```css
/* Style */
#mySVG * {
fill-opacity: 0;
transition: fill-opacity 1s;
}
#mySVG.finished * {
fill-opacity: 1;
}
```
```js
/* JavaScript */
new Vivus('mySVG', {}, function (obj) {
obj.el.classList.add('finished');
});
```
**WARNING**: This hack cannot work on sandboxed solutions like the use of `object` tag.

View File

@ -0,0 +1,517 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>vivus.js - svg animation</title>
<meta name="description" content="SVG Drawing Animation" />
<style type="text/css">
/* Base style */
html {
font-size: 24px;
}
body {
margin: 0 0 40px;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
font-weight: 200;
color: #666666;
background-color: #FFFFFF;
word-break: break-word;
}
a, a:visited, a:hover, a:link {
color: inherit;
outline: 0;
}
small {
font-weight: 100;
}
p {
font-size: 1rem;
line-height: 1.4rem;
}
button, .button {
margin: 0; padding: 3px 6px;
border-radius: 6px;
border: 1px solid currentColor;
color: inherit;
background-color: rgba(0,0,0,0);
font-size: 0.6rem;
font-weight: 300;
text-decoration: none;
cursor: pointer;
outline: 0;
}
button.active, .button.active {
background-color: currentColor;
}
button.active span, .button.active span {
color: #FFFFFF;
}
i {
background-color: rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
svg * {
fill: none;
stroke: currentColor;
}
/* Common components */
.bloc {
color: #f9f9f9;
padding: 1px 0 30px;
clear: both;
}
.content {
margin: auto;
max-width: 960px;
padding: 0 20px;
}
.col3 {
width: 33.33%;
float: left;
text-align: center;
border-bottom-color: currentColor;
border-bottom-style: solid;
}
.col-container {
padding: 0 12px;
}
.col3 p {
font-size: 0.75rem;
line-height: 1.05rem;
}
/* Text */
.bigger {
font-size: 1rem;
font-weight: 100;
line-height: 1.4rem;
}
.center {
text-align: center;
}
.clearer {
clear: both;
}
.striked {
text-decoration: line-through;
}
.italic {
font-style: italic;
}
/* Blocs */
.bloc-head { color: #5aa8c5; padding: 30px; }
.bloc-demo { color: #FF495F; }
.bloc-timing { color: #F7A800; }
.bloc-scenario { color: #4fe084; }
.bloc-doc { color: #69B0CA; }
/* Custom */
#hi-there {
width: 100%;
stroke-width:4;
}
.intro-links {
text-align: right;
}
.example-description {
margin-left: 440px;
}
.timing-description {
min-height: 200px;
margin-right: 240px;
}
.obturateur {
stroke-width: 3;
stroke-miterlimit: 10;
}
#polaroid {
float: left;
width: 400px; height: 320px;
stroke: #f9f9f9;
stroke-width: 3;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10;
}
#timing-example {
float: right;
width: 175px; height: 175px;
}
.goodbye-head {
font-size: 1.5rem;
text-align: center;
margin-bottom: 0;
}
.goodbye-sub {
font-size: 0.875rem;
text-align: center;
margin: 0 0 20px;
}
/* Media queries */
@media (max-width: 960px) {
.button-group { display: block; line-height: 1.8em; }
}
@media (min-width: 768px) {
.col3 { border-bottom: none; }
}
@media (max-width: 767px) {
#polaroid { width: 100%; max-height: 300px; }
.example-description { margin-left: 0; }
.timing-description { margin-right: 0; }
#timing-example { float: none; width: 100%; }
.col3 { width: 100%; float: none; margin-bottom: 25px; padding-bottom: 25px; border-bottom-width: 1px; min-height: 200px; }
.col3:last-child { margin-bottom: 0; padding-bottom: 0; border-bottom-width: 0px; }
}
@media (min-width: 481px) and (max-width: 767px) {
#polaroid { width: 100%; max-height: 300px; }
.example-description { margin-left: 0; }
.timing-description { margin-right: 0; }
.col3:nth-child(2n) svg {
width: 200px;
float: right;
}
.col3:nth-child(2n) .col-container {
text-align: right;
margin-right: 200px;
}
.col3:nth-child(2n+1) svg {
width: 200px;
float: left;
}
.col3:nth-child(2n+1) .col-container {
text-align: left;
margin-left: 200px;
}
}
@media (max-width: 480px) {
.col-container { padding: 0px; }
#polaroid { width: 100%; max-height: 260px; }
}
</style>
</head>
<body>
<!-- Head: HI THERE -->
<div class="bloc bloc-head">
<svg height="300" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 404.7 354" enable-background="new 0 0 404.7 354" id="hi-there" onclick="hi.reset().play();">
<!-- HI -->
<path data-duration="10" d="M324.6,61.2c16.6,0,29.5-12.9,29.5-29.5c0-16.6-12.9-29.5-29.5-29.5c-16.6,0-29.5,12.9-29.5,29.5C295.1,48.4,308,61.2,324.6,61.2z"/>
<path data-duration="130" d="M366.2,204.2c-9.8,0-15-5.6-15-15.1V77.2h-85v28h19.5c9.8,0,8.5,2.1,8.5,11.6v72.4c0,9.5,0.5,15.1-9.3,15.1H277h-20.7c-8.5,0-14.2-4.1-14.2-12.9V52.4c0-8.5,5.7-12.3,14.2-12.3h18.8v-28h-127v28h18.1c8.5,0,9.9,2.1,9.9,8.9v56.1h-75V53.4c0-11.5,8.6-13.3,17-13.3h11v-28H2.2v28h26c8.5,0,12,2.1,12,7.9v142.2c0,8.5-3.6,13.9-12,13.9h-21v33h122v-33h-11c-8.5,0-17-4.1-17-12.2v-57.8h75v58.4c0,9.1-1.4,11.6-9.9,11.6h-18.1v33h122.9h5.9h102.2v-33H366.2z"/>
<path data-async data-delay="20" d="M358.8,82.8c11.1-4.2,18.8-14.7,18.8-27.5c0-8.5-3.4-16-8.9-21.3"/>
<path data-async d="M124.2,105.7V77c0-11.5,9.1-13.8,17.5-13.8h10.5V44.7"/>
<polyline data-async points="147.9,40.2 171.2,63.2 175.7,63.2"/>
<line data-async x1="295.1" y1="32.1" x2="275.2" y2="12.2"/>
<path data-async d="M266.2,204.7V75.9c0-8.5,5.2-12.8,13.7-12.8h18.3V44.7"/>
<polyline data-async points="265.9,105.2 289.2,129.2 293.7,129.2"/>
<polyline data-async points="374.2,204.7 374.2,94.2 358.8,82.8 351.2,77.2"/>
<polyline data-async points="148.2,237.2 171.2,261.2 294.6,261.2 300.5,261.2 402.2,261.2 402.2,228.2 379.2,204.2"/>
<polyline data-async points="124.2,204.7 124.2,157.2 175.7,157.2"/>
<line data-async x1="147.7" y1="228.2" x2="129.2" y2="204.2"/>
<polyline data-async points="7.2,237.3 30.2,261.2 152.2,261.2 152.2,241.7"/>
<polyline data-async points="1.9,40.2 26,63.2 39.7,63.2"/>
<line data-async x1="129.2" y1="12.2" x2="148.2" y2="33.2"/>
<line data-async x1="303.9" y1="53" x2="328.1" y2="77.2"/>
<line x1="345.1" y1="10.5" x2="368.7" y2="34"/>
<!-- there -->
<path data-delay="30" data-duration="60" stroke-linecap="round" stroke-linejoin="round" d="M76.8,337.3c0,0,1.9,12.2,13.1,12.2c22.1,0,23.8-1.8,59-66.4c-19.7,35.7-36.4,66.2-19.3,66.2c15.2,0,22.9-14.2,28.3-23.7c3.3-0.5,24-3.2,35-25.5c4-8.1,4.1-17.8-8.1-15.2c-5.6,1.2-13.1,14.8-15.7,19.2c-7.6,12.7-22.4,45.2-22.4,45.2s10.3-22.4,21.5-22.4c15.5,0-9.4,22.4,4.7,22.4c4.9,0,11.7-11.4,16.6-20.9c7.5,4.7,19.7,1.7,24.5-8.1c10.1-20.4-14.4-12.8-24.5,8.1c-5.5,11.3-2.2,21.1,11.2,21.1c16.4,0,26.1-28.3,30.5-37.5c9.9,2.5,14,2.5,22.7-1.1c-3.5,5.1-24,38.1-8.3,38.1c6.7,0,11.7-11.4,16.6-20.9c7.5,4.7,19.7,1.7,24.5-8.1c10.1-20.4-14.4-12.8-24.5,8.1c-5.5,11.3-2.2,21.1,11.2,21.1c16.4,0,20.6-4,24.7-10.5"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M157.3,300.8c3.8-2.3-29,0.8-35.6,3.2"/>
</svg>
</div>
<!-- Intro -->
<div class="content">
<h1>vivus<small>, bringing your SVGs to life</small></h1>
<p>Vivus is a lightweight JavaScript class (with no dependencies) that allows you to animate SVGs, giving them the appearence of being drawn. There are a variety of different animations available, as well as the option to create a custom script to draw your SVG in whatever way you like.</p>
<p class="intro-links">
<a href="//github.com/maxwellito/vivus" class="button bigger">View on GitHub</a>
</p>
</div>
<!-- Animation examples/demo -->
<div class="bloc bloc-demo">
<div class="content">
<h2>Animation types</h2>
<div>
<div class="col3">
<svg id="obturateur1" class="obturateur" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="100%" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" onclick="obt1.reset().play();">
<circle cx="100" cy="100" r="90"/>
<circle cx="100" cy="100" r="85.74"/>
<circle cx="100" cy="100" r="72.947"/>
<circle cx="100" cy="100" r="39.74"/>
<line x1="34.042" y1="131.189" x2="67.047" y2="77.781"/>
<line x1="31.306" y1="75.416" x2="92.41" y2="60.987"/>
<line x1="68.81" y1="34.042" x2="122.219" y2="67.046"/>
<line x1="124.584" y1="31.305" x2="139.013" y2="92.409"/>
<line x1="165.957" y1="68.809" x2="132.953" y2="122.219"/>
<line x1="168.693" y1="124.584" x2="107.59" y2="139.012"/>
<line x1="131.19" y1="165.957" x2="77.781" y2="132.953"/>
<line x1="75.417" y1="168.693" x2="60.987" y2="107.59"/>
</svg>
<div class="col-container">
<h3>Delayed</h3>
<p>Every path element is drawn at the same time with a small delay at the start. This is currently the default animation.</p>
<button onclick="obt1.reset().play();">replay</button>
</div>
</div>
<div class="col3">
<svg id="obturateur2" class="obturateur" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="100%" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" onclick="obt2.reset().play();">
<circle cx="100" cy="100" r="90"/>
<circle cx="100" cy="100" r="85.74"/>
<circle cx="100" cy="100" r="72.947"/>
<circle cx="100" cy="100" r="39.74"/>
<line x1="34.042" y1="131.189" x2="67.047" y2="77.781"/>
<line x1="31.306" y1="75.416" x2="92.41" y2="60.987"/>
<line x1="68.81" y1="34.042" x2="122.219" y2="67.046"/>
<line x1="124.584" y1="31.305" x2="139.013" y2="92.409"/>
<line x1="165.957" y1="68.809" x2="132.953" y2="122.219"/>
<line x1="168.693" y1="124.584" x2="107.59" y2="139.012"/>
<line x1="131.19" y1="165.957" x2="77.781" y2="132.953"/>
<line x1="75.417" y1="168.693" x2="60.987" y2="107.59"/>
</svg>
<div class="col-container">
<h3>Sync</h3>
<p>Each line is drawn synchronously. They all start and finish at the same time, hence the name `sync`.</p>
<button onclick="obt2.reset().play();">replay</button>
</div>
</div>
<div class="col3">
<svg id="obturateur3" class="obturateur" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="100%" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" onclick="obt3.reset().play();">
<circle cx="100" cy="100" r="90"/>
<circle cx="100" cy="100" r="85.74"/>
<circle cx="100" cy="100" r="72.947"/>
<circle cx="100" cy="100" r="39.74"/>
<line x1="34.042" y1="131.189" x2="67.047" y2="77.781"/>
<line x1="31.306" y1="75.416" x2="92.41" y2="60.987"/>
<line x1="68.81" y1="34.042" x2="122.219" y2="67.046"/>
<line x1="124.584" y1="31.305" x2="139.013" y2="92.409"/>
<line x1="165.957" y1="68.809" x2="132.953" y2="122.219"/>
<line x1="168.693" y1="124.584" x2="107.59" y2="139.012"/>
<line x1="131.19" y1="165.957" x2="77.781" y2="132.953"/>
<line x1="75.417" y1="168.693" x2="60.987" y2="107.59"/>
</svg>
<div class="col-container">
<h3>OneByOne</h3>
<p>Each path element is drawn one after the other. This animation gives the best impression of live drawing.</p>
<button onclick="obt3.reset().play();">replay</button>
</div>
</div>
</div>
<div class="clearer"></div>
</div>
</div>
<!-- Scripting -->
<div class="bloc bloc-timing">
<div class="content">
<h2>Timing function</h2>
<p>To give more freedom, it's possible to override the animation of each path and/or the entire SVG. It works a bit like the CSS animation timing function. But instead of using a cubic-bezier function, it use a simple JavaScript function. It must accept a number as parameter (between 0 to 1), then return a number (also between 0 and 1). It's a hook.</p>
<p>Here an example test to play around with the different properties available.</p>
<div>
<svg id="timing-example" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 200 200" enable-background="new 0 0 200 200" xml:space="preserve" onclick="timing&&timing.reset().play();">
<g stroke-width="4" stroke-linecap="round" stroke-miterlimit="10">
<line x1="68.18066" y1="68.18066" x2="131.81934" y2="131.81934"/>
<line x1="68.18066" y1="131.82031" x2="131.81934" y2="68.17969"/>
<circle cx="100" cy="100" r="65"/>
<circle cx="100" cy="100" r="75"/>
<circle cx="100" cy="100" r="85"/>
<circle cx="100" cy="100" r="95"/>
</g>
</svg>
<div class="timing-description">
<p>Type
<span class="button-group">
<button onclick="timingTest(this,'type','delayed');" class="active"><span>delayed</span></button>
<button onclick="timingTest(this,'type','sync');"><span>sync</span></button>
<button onclick="timingTest(this,'type','oneByOne');"><span>oneByOne</span></button>
</span>
</p>
<p>Path timing function
<span class="button-group">
<button onclick="timingTest(this,'path','LINEAR');" class="active"><span>linear</span></button>
<button onclick="timingTest(this,'path','EASE');"><span>ease</span></button>
<button onclick="timingTest(this,'path','EASE_IN');"><span>ease-in</span></button>
<button onclick="timingTest(this,'path','EASE_OUT');"><span>ease-out</span></button>
<button onclick="timingTest(this,'path','EASE_OUT_BOUNCE');"><span>ease-out bounce</span></button>
</span>
</p>
<p>Anim timing function
<span class="button-group">
<button onclick="timingTest(this,'anim','LINEAR');" class="active"><span>linear</span></button>
<button onclick="timingTest(this,'anim','EASE');"><span>ease</span></button>
<button onclick="timingTest(this,'anim','EASE_IN');"><span>ease-in</span></button>
<button onclick="timingTest(this,'anim','EASE_OUT');"><span>ease-out</span></button>
<button onclick="timingTest(this,'anim','EASE_OUT_BOUNCE');"><span>ease-out bounce</span></button>
</span>
</p>
</div>
</div>
</div>
</div>
<!-- Scripting example -->
<div class="bloc bloc-scenario">
<div class="content">
<div class="script-example">
<svg id="polaroid" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 200 160" enable-background="new 0 0 200 160" onclick="pola.reset().play();">
<!-- Case -->
<!-- The case items will be drawn at the same time (attribute `data-async` on each tag) with a custom duration of 40 frames (attribute `data-duration`). WARNING: When you want to draw a bloc asynchronously (like here), the last item need to be `data-async` free. Otherwise the following tags will also start at the same time. I know it's a bit confusing, play a bit with it and you'll see. -->
<path data-async data-duration="40" d="
M106.725,104.742c-0.773,2.498-3.586,4.229-6.285,3.867L12.473,96.802c-2.699-0.363-4.262-2.682-3.49-5.18l25.164-81.436
c0.771-2.496,3.584-4.229,6.283-3.866l87.966,11.808c2.699,0.362,4.264,2.68,3.49,5.179L106.725,104.742z"/>
<path data-async data-duration="40" d="
M101.02,123.207c-0.773,2.5-3.587,4.23-6.286,3.867L6.766,115.27c-2.699-0.363-4.26-2.682-3.488-5.182l2.91-9.417
c0.771-2.499,3.585-4.23,6.285-3.87l87.967,11.809c2.699,0.361,4.261,2.682,3.49,5.18L101.02,123.207z"/>
<line data-async data-duration="40" x1="103.377" y1="128.225" x2="154.768" y2="155.32"/>
<line data-async data-duration="40" x1="109.852" y1="112.684" x2="155.035" y2="136.906"/>
<path data-async data-duration="40" d="
M9.096,120.207l47.932,21.994c0,0,98.06,12.414,97.74,13.119c-0.318,0.709,5.426-16.205,5.426-16.205l-2.646-96.842
c-1.098-7.587-2.467-11.8-8.559-15.024l-12.635-6.604"/>
<path data-async data-duration="40" d="
M161.586,38.135l30.717,16.085c0,0,5.295,2.323,4.543,6.504l-3.215,10.527c-0.648,2.621-2.939,4.988-8.229,2.798l-9.154-4.701
l-11.834,56.441"/>
<path data-duration="40" d="
M183.148,49.518c0,0,5.295,2.324,4.543,6.506l-3.215,10.526c-0.648,2.622-2.938,4.988-8.229,2.798"/>
<!-- Lens -->
<!-- All item will be drawn line by line, with an exception for the first one, a little delay (attribute `data-delay) to make a break between the drawing of the case and the start of the lens part -->
<path data-delay="20" d="
M87.176,56.143C83.274,68.78,69.043,77.538,55.395,75.706S33.846,62.146,37.75,49.511c3.903-12.637,18.135-21.392,31.783-19.562
C83.181,31.782,91.081,43.51,87.176,56.143z"/>
<path d="
M92.745,56.891c-4.785,15.48-22.219,26.213-38.942,23.969C37.079,78.615,27.4,64.245,32.184,48.763
c4.783-15.48,22.218-26.211,38.94-23.968C87.848,27.041,97.528,41.411,92.745,56.891z"/>
<path d="
M78.99,26.933c16.169,7.426,19.398,10.989,22.026,20.105c1.283,4.449,1.271,9.411-0.3,14.489
c-4.783,15.479-22.217,26.213-38.941,23.969c-8.68-1.165-21.171-7.963-25.613-14.055"/>
<path d="
M42.602,50.162c3.137-10.157,14.573-17.193,25.543-15.722"/>
<!-- Flash -->
<!-- This tag does not have any extra attribute. So it will start when the previous tag is finished. His duration and delay will be the one given in the options. -->
<path d="
M103.789,29.275c-0.568,1.841,0.582,3.549,2.57,3.818l12.807,1.72c1.988,0.266,4.062-1.012,4.633-2.851l1.66-5.38
c0.568-1.843-0.582-3.551-2.57-3.816l-12.807-1.72c-1.988-0.268-4.062,1.01-4.633,2.85L103.789,29.275z"/>
<!-- Output -->
<!-- Same case as Flash -->
<path d="
M11.129,105.791c-0.297,0.965,0.305,1.855,1.346,1.994l81.446,10.932c1.038,0.141,2.123-0.527,2.42-1.49l0,0
c0.298-0.961-0.304-1.855-1.343-1.996l-81.447-10.93C12.51,104.16,11.426,104.828,11.129,105.791L11.129,105.791z"/>
<!-- Design (color lines on the front) -->
<!-- All the lines will start at the same time, because they all have the attribute `data-async` -->
<line data-async x1="47.583" y1="101.505" x2="51.561" y2="88.267"/>
<line data-async x1="53.391" y1="102.326" x2="57.047" y2="90.125"/>
<line data-async x1="59.224" y1="103.068" x2="62.749" y2="91.295"/>
<line data-async x1="65.057" y1="103.814" x2="69.015" y2="90.637"/>
<line data-async x1="72.87" y1="19.969" x2="75.497" y2="11.082"/>
<line data-async x1="78.512" y1="21.325" x2="81.317" y2="11.868"/>
<line data-async x1="83.833" y1="23.718" x2="87.16" y2="12.582"/>
<line data-async x1="89.145" y1="26.141" x2="92.939" y2="13.498"/>
</svg>
<div class="example-description">
<h2>Scenarize</h2>
<p>This feature allows you to script the animation of your SVG. To do this, the custom values will be set directly in the DOM of the SVG.</p>
<p>Here is an example using <i>scenario-sync</i>.<br>I would recommend you look at the source code and the readme file for more information.</p>
<button onclick="pola.reset().play();">replay</button>
<button onclick="pola.play(-3);">rewind</button>
</div>
<div class="clearer"></div>
</div>
</div>
</div>
<!-- Info and documentation link -->
<div class="bloc bloc-doc">
<div class="content">
<p class="center">Play with it on <a href="https://maxwellito.github.io/vivus-instant/">Vivus instant</a>.</p>
<p class="center">More information and documentation on <a href="https://github.com/maxwellito/vivus#vivusjs">GitHub</a>.</p>
</div>
</div>
<!-- Goodbye -->
<div class="content">
<p class="goodbye-head">Thanks for watching.</p>
<p class="goodbye-sub">Made with <span class="striked">love</span> <span class="italic">a keyboard</span></p>
</div>
<!-- Le scripts -->
<script src="dist/vivus.js"></script>
<script>
function easeOutBounce (x) {
var base = -Math.cos(x * (0.5 * Math.PI)) + 1;
var rate = Math.pow(base,1.5);
var rateR = Math.pow(1 - x, 2);
var progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI) )) + 1;
return (1- rateR) + (progress * rateR);
}
var timing,
timingProps = {
type: 'delayed',
duration: 150,
start: 'autostart',
pathTimingFunction: Vivus.LINEAR,
animTimingFunction: Vivus.LINEAR
};
function timingTest (buttonEl, property, type) {
var activeSibling = buttonEl.parentNode.querySelector('button.active');
activeSibling.classList.remove('active');
buttonEl.classList.add('active');
timingProps.type = (property === 'type') ? type : timingProps.type;
timingProps.pathTimingFunction = (property === 'path') ? Vivus[type] : timingProps.pathTimingFunction;
timingProps.animTimingFunction = (property === 'anim') ? Vivus[type] : timingProps.animTimingFunction;
timing && timing.stop().destroy();
timing = new Vivus('timing-example', timingProps);
}
var hi = new Vivus('hi-there', {type: 'scenario-sync', duration: 20, start: 'autostart', dashGap: 20, forceRender: false},
function () {
if (window.console) {
console.log('Animation finished. [log triggered from callback]');
}
}),
obt1 = new Vivus('obturateur1', {type: 'delayed', duration: 150}),
obt2 = new Vivus('obturateur2', {type: 'sync', duration: 150}),
obt3 = new Vivus('obturateur3', {type: 'oneByOne', duration: 150}),
pola = new Vivus('polaroid', {type: 'scenario-sync', duration: 20, forceRender: false});
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
{
"name": "vivus",
"version": "0.4.6",
"description": "JavaScript library to make drawing animation on SVG",
"main": "dist/vivus.js",
"scripts": {
"test": "karma start test/karma.conf.js",
"serve": "python -m SimpleHTTPServer 8844",
"lint": "./node_modules/eslint/bin/eslint.js src test",
"build": "npm run build-raw && npm run build-min",
"build-raw": "node src/_build.js > dist/vivus.js",
"build-min": "uglifyjs dist/vivus.js -o dist/vivus.min.js -c -m --source-map"
},
"repository": {
"type": "git",
"url": "https://github.com/maxwellito/vivus.git"
},
"author": "maxwellito",
"license": "MIT",
"bugs": {
"url": "https://github.com/maxwellito/vivus/issues"
},
"homepage": "https://github.com/maxwellito/vivus",
"engine": {
"node": ">=0.10.22"
},
"devDependencies": {
"eslint": "^5.15.3",
"karma": "^4.0.1",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage": "^1.1.2",
"karma-jasmine": "^2.0.1",
"uglify-js": "^3.5.2"
}
}

View File

@ -0,0 +1,352 @@
# vivus.js
Demo available on http://maxwellito.github.io/vivus
Play with it on [Vivus Instant](https://maxwellito.github.io/vivus-instant/)
Vivus is a lightweight JavaScript class (with no dependencies) that allows you to animate SVGs, giving them the appearance of being drawn. There are a variety of different animations available, as well as the option to create a custom script to draw your SVG in whatever way you like.
Available via:
- [NPM](https://www.npmjs.com/package/vivus): `npm install vivus`
- [Bower](http://bower.io/): `bower install vivus`
- [jsDelivr CDN](http://www.jsdelivr.com/#!vivus): `//cdn.jsdelivr.net/npm/vivus@latest/dist/vivus.min.js`
- [CDNJS CDN](https://cdnjs.com/libraries/vivus)
- [WebJars](http://www.webjars.org/)
Join the conversation on [Gitter](https://gitter.im/maxwellito/vivus)
Try Vivus with your SVG on [Vivus Instant](https://maxwellito.github.io/vivus-instant/). If you plan to use the library to animate a single SVG without callback or controls, this will allow you to download your animated SVG, powered by CSS, JavaScript free.
## Animations
On the following images, the pink color represents the `duration` value, and the blue one is for `delay` value.
### Delayed
![Timeline for delayed animation](https://raw.github.com/maxwellito/vivus/master/assets/delayed.png)
Every path element is drawn at the same time with a small delay at the start. This is currently the default animation.
### Sync
![Timeline for sync animation](https://raw.github.com/maxwellito/vivus/master/assets/sync.png)
Each line is drawn synchronously. They all start and finish at the same time, hence the name `sync`.
### OneByOne
![Timeline for oneByOne animation](https://raw.github.com/maxwellito/vivus/master/assets/oneByOne.png)
Each path element is drawn one after the other. This animation gives the best impression of live drawing. The duration for each line depends on their length to make a constant drawing speed.
## Principles
To get this effect, the script uses the CSS property `strokeDashoffset`. This property manages the stroke offset on every line of the SVG. Now, all we have to do is add some JavaScript to update this value progressively and the magic begins.
However, there's a problem with this. The `strokeDashoffset` property is only available on the path elements. This is an issue because in an SVG there are a lot of elements such as `circle`, `rect`, `line` and `polyline` which will break the animation. So to fix this, there is another class available in the repo called `pathformer`. It's made for transforming all objects of your SVG into `path` elements to be able to use `strokeDashoffset` and animate your SVGs.
_The animation always draws elements in the same order as they are defined in the SVG tag._
There are few conditions that your SVG must meet:
- All elements must have a stroke property and cannot be filled. This is because the animation only looks to progressively draw strokes and will not check for filled colours. For example: fill: "none"; stroke: "#FFF";
- You should avoid creating any hidden path elements in your SVG. Vivus considers them all eligible to be animated, so it is advised to remove them before playing with it. If they are not removed the animation might not achieve the desired effect, with blank areas and gaps appearing.
- `text` elements aren't allowed, they cannot be transformed into `path` elements. See [#22](https://github.com/maxwellito/vivus/issues/22) for more details.
The code is inspired from other repositories. The drawer is inspired from the excellent [Codrops](http://tympanus.net/codrops/) about the post [SVG Drawing Animation](http://tympanus.net/codrops/2013/12/30/svg-drawing-animation/) (if you don't know this website, get ready to have your mind blown). Then for the pathformer, there is a lot of work from [SVGPathConverter](https://github.com/Waest/SVGPathConverter) by [Waest](https://github.com/Waest).
## Usage
As I said, no dependencies here. All you need to do is include the scripts.
**Inline SVG**
```html
<svg id="my-svg">
<path...>
<path...>
<path...>
</svg>
<script>
new Vivus('my-svg', {duration: 200}, myCallback);
</script>
```
**Dynamic load**
```html
<object id="my-svg" type="image/svg+xml" data="link/to/my.svg"></object>
<script>
new Vivus('my-svg', { duration: 200 }, myCallback);
</script>
```
or
```html
<div id="my-div"></div>
<script>
new Vivus('my-div', { duration: 200, file: 'link/to/my.svg' }, myCallback);
</script>
```
By default the `object` created will take the size of the parent element, this one must have a height and width or your SVG might not appear.
If you need to edit this object, it is accessible in the `onReady` callback:
```js
new Vivus('my-div-id', {
file: 'link/to/my.svg',
onReady: function (myVivus) {
// `el` property is the SVG element
myVivus.el.setAttribute('height', 'auto');
}
});
```
Check out the [hacks page](https://github.com/maxwellito/vivus/blob/master/hacks.md) for more tricks.
### Constructor
The Vivus constructor asks for 3 parameters:
- ID (or object) of DOM element to interact with.<br/>It can be an inline SVG or a wrapper element to append an object tag from the option `file`
- Option object (described in the following |
- Callback to call at the end of the animation (optional)
### Option list
| Name | Type | Description |
| -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Defines what kind of animation will be used: `delayed`, `sync`, `oneByOne`, `script`, `scenario` or `scenario-sync`. [Default: `delayed`] |
| `file` | string | Link to the SVG to animate. If set, Vivus will create an object tag and append it to the DOM element given to the constructor. Be careful, use the `onReady` callback before playing with the Vivus instance. |
| `start` | string | Defines how to trigger the animation (`inViewport` once the SVG is in the viewport, `manual` gives you the freedom to call draw method to start, `autostart` makes it start right now). [Default: `inViewport`] |
| `duration` | integer | Animation duration, in frames. [Default: `200`] |
| `delay` | integer | Time between the drawing of first and last path, in frames (only for `delayed` animations). |
| `onReady` | function | Function called when the instance is ready to play. |
| `pathTimingFunction` | function | Timing animation function for each path element of the SVG. Check the [timing function part](#timing-function). |
| `animTimingFunction` | function | Timing animation function for the complete SVG. Check the [timing function part](#timing-function). |
| `dashGap` | integer | Whitespace extra margin between dashes. Increase it in case of glitches at the initial state of the animation. [Default: `2`] |
| `forceRender` | boolean | Force the browser to re-render all updated path items. By default, the value is `true` on IE only. (check the 'troubleshoot' section for more details). |
| `reverseStack` | boolean | Reverse the order of execution. The default behaviour is to render from the first 'path' in the SVG to the last one. This option allow you to reverse the order. [Default: `false`] |
| `selfDestroy` | boolean | Removes all extra styling on the SVG, and leaves it as original. |
### Methods
| Name | Description |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `play(speed, callback)` | Plays the animation with the speed given in parameter. This value can be negative to go backward, between 0 and 1 to go slowly, >1 to go faster, or <0 to go in reverse from current state. [Default: `1`]. Callback executed after the animation is finished (optional) |
| `stop()` | Stops the animation. |
| `reset()` | Reinitialises the SVG to the original state: undrawn. |
| `finish()` | Set the SVG to the final state: drawn. |
| `setFrameProgress(progress)` | Set the progress of the animation. Progress must be a number between 0 and 1. |
| `getStatus()` | Get the status of the animation between `start`, `progress`, `end` |
| `destroy()` | Reset the SVG but make the instance out of order. |
These methods return the object so you can chain the actions.
```js
const myVivus = new Vivus('my-svg-id');
myVivus.stop().reset().play(2);
```
#### Play method callback
Instead of using the global constructor callback when you create the Vivus object, you can add callbacks to be
executed for specific `play` method calls.
```js
const myVivus = new Vivus('my-svg-id');
myVivus.play(1, function () {
// called after the animation completes
});
// alternativly if you leave the speed param blank and use the default, you
// can pass the callback as the first parameter like so.
myVivus.play(function () {
// called after the animation completes
});
```
## Timing function
To give more freedom, it's possible to override the animation of each path and/or the entire SVG. It works a bit like the CSS animation timing function. But instead of using a cubic-bezier function, it use a simple JavaScript function. It must accept a number as parameter (between 0 to 1), then return a number (also between 0 and 1). It's a hook.
If you don't want to create your own, timing methods are available via the constructor object: `EASE`, `EASE_IN`, `EASE_OUT` and `EASE_OUT_BOUNCE`. Then set it in the option object to enjoy them.
```js
// Here, the ease animation will be use for the global drawing.
new Vivus(
'my-svg-id',
{
type: 'delayed',
duration: 200,
animTimingFunction: Vivus.EASE
},
myCallback
);
```
**WARNING**: `animTimingFunction` is called at every frame of the animation, and `pathTimingFunction` is also called at every frame for each path of your SVG. So be careful about them. Keep it simple, or it can affect the performance.
## Extra attributes
The attribute `data-ignore` allows you to ignore path tags from the vivus animation.
```html
<svg id="my-svg">
<path...>
<path data-ignore="true" ...>
<path...>
</svg>
```
In this case, the second path won't be part of the animation.
## Scenarize
This feature allows you to script the animation of your SVG. For this, the custom values will be set directly in the DOM of the SVG.
### `scenario`
This type is easier to understand, but longer to implement. You just have to define the start and duration of each element with `data-start` and `data-duration` attributes. If it is missing, it will use the default value given to the constructor.
The best part of this type is the flexibility it provides. You don't have to respect the order/stack of the SVG and you can start with the last element, then continue with the first to finish with all the rest at the same time.
You will then have to define custom rules for each element in your SVG via extra attributes in your SVG DOM :
- `data-start` (integer)
time when the animation must start, in frames
- `data-duration` (integer)
animation duration of this path, in frames
```html
<svg>
<path data-start="0" data-duration="10" ... />
<path data-start="20" data-duration="10" ... />
<path data-start="20" data-duration="20" ... />
<path data-start="0" data-duration="30" ... />
</svg>
```
### `scenario-sync`
It's not the sexiest code ever, but it's quite flexible. In addition to this, the behaviour is fairly different.
By using this animation type, the default behaviour is the same as `oneByOne`. However, you can define some properties on a specific path item such as the duration, the delay to start (from the end of the previous path) and if it should be played synchronously.
- `data-delay` (integer)
time between the end of the animation of the previous path and the start of the current path, in frames
- `data-duration` (integer)
duration of this path animation, in frames
- `data-async` (no value required)
make the drawing of this path asynchronous. It means the next path will start at the same time.
If a path does not have an attribute for duration or delay then the default values, set in the options, will be used.
Example: here is a simple SVG containing 5 elements. With the following options `{duration: 20, delay: 0}`, we should get this timeline
![Timeline for script animation by default](https://raw.github.com/maxwellito/vivus/master/assets/script_default.png)
This looks like 'oneByOne' animation, synchronous mode. But to make it a bit custom, here is what I can do:
```html
<svg>
<path data-duration="10" ... />
<path data-delay="10" data-async ... />
<path data-delay="15" ... />
<path data-duration="10" data-delay="45" data-async ... />
<path data-duration="50" data-delay="5" ... />
</svg>
```
This scenario should give us
![Timeline for this custom script animation](https://raw.github.com/maxwellito/vivus/master/assets/script_custom.png)
I'm sorry if it does not look very sexy, and it's not really easy to use. I'm happy to make any changes, as long as the idea sounds interesting. Post an issue and I'll be very happy to talk about it!
## Non Scaling
Some SVG elements might use non scaling properties such as `vector-effect="non-scaling-stroke"`, which requires some additional custom logic. On instance construction Vivus will map all the child elements in the SVG and calculate their line length. If the element is resized during the animation, the calculated stroke style properties become invalid and the SVG will display incorrectly.
To keep animation consistency, the method `recalc` should be called when the SVG is resized. It will re-calculate the line length on affected child elements on the next frame calculation.
Code example:
```js
// Create your Vivus instance
const vivusObject = new Vivus('my-div', {
duration: 200,
file: 'link/to/my.svg',
});
// Create your observer and set up a callback on resize
const resizeObserver = new ResizeObserver((entries) => {
// Recalculate the line lengths
vivusObject.recalc();
});
resizeObserver.observe(vivusObject.el);
```
Vivus will provide a warning in the console when it detects stroke scaling.
## Development
To make it simpler a gulp file is set up to automise minifying, JShint and tests.
If you have never used Gulp before this is a good opportunity. To use it, you need to install NodeJS first then run `sudo npm install -g gulp`.
To start, you will need to install the repo dependencies:
```bash
$ npm install
```
Then you can use NPM scripts to run the following tasks:
- `build` make the build (generate `dist/vivus.js` and `dist/vivus.min.js`)
- `lint` run ESlint on the source files
- `test` run Karma
## Troubleshoot
### Internet Explorer
Some SVG weren't working at all. The only solution found was to clone and replace each updated path element. Of course this solution requires more resources and a lot of DOM manipulation, but it will give a smooth animation like other browsers. This fallback is only applied on Internet Explorer (all versions), and can be disabled via the option `forceRender`.
Replacing each updated path by a clone was the only way to force IE to re-render the SVG. On some SVGs this trick is not necessary, but IE can be a bit tricky with this. If you're worried about performance, I would recommend checking if your SVG works correctly by disabling the `forceRender` option. If it works correctly on IE, then keep it like this.
By default, `forceRender` is `true` on Internet Explorer only.
### Firefox
For Firefox users, you might encounter some glitches depending on your SVG and browser version. On versions before 36, there is a problem retrieving path length via `getTotalLength` method. Returning 174321516544 instead of 209 (I'm not exaggerating, this comes from a real case), messing up the entire animation treatment. Unfortunately, there's nothing that this library can do, this is due to Firefox. I hope to find a workaround, but at the moment I can only recommend that you test your animation on previous versions of Firefox.
## Debug
For an easier debug have a look to the attribute `map` of your Vivus object. This contains the mapping of your animation. If you're using a modern browser, I recommend `console.table` to get a nice output of the array which will make your debug easier.
```javascript
const logo = new Vivus('myLogo', { type: 'scenario-sync' });
// The property 'map' contain all the SVG mapping
console.table(logo.map);
```
## Special thanks!
Thanks to all contributors! Also users who pushed me to improve the library by publishing it on NPM, or browser compatibility or features. Also thanks for fixing my awful english :)
- [@jolic](https://github.com/jolic) for dynamic SVG loading, ignore invisible paths, infinite and beyond...
- [@BenMcGeachy](https://github.com/BenMcGeachy) for making the documentation understandable
- [@TranscendOfSypherus](https://github.com/TranscendOfSypherus) for fixing the PathFormer
- [@flyingfisch](https://github.com/flyingfisch) for general helping with issues
- [@morgangiraud](https://github.com/morgangiraud) on the ignore invisible paths
- [@Nerdissimo](https://github.com/Nerdissimo) for inserting SVG without `object` wrapper
- [@jsimnz](https://github.com/jsimnz) for adding callbacks to play method
and many others...

View File

@ -0,0 +1,36 @@
var fs = require('fs');
var pkg = require('../package.json');
var vivus = fs.readFileSync('src/vivus.js', { encoding: 'utf8' });
var pathformer = fs.readFileSync('src/pathformer.js', { encoding: 'utf8' });
var output = `/**
* ${pkg.name} - ${pkg.description}
* @version v${pkg.version}
* @link ${pkg.homepage}
* @license ${pkg.license}
*/
(function () {
${pathformer}
${vivus}
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], function() {
return Vivus;
});
} else if (typeof exports === 'object') {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = Vivus;
} else {
// Browser globals
window.Vivus = Vivus;
}
}());
`;
console.log(output);

View File

@ -0,0 +1,276 @@
'use strict';
/**
* Pathformer
* Beta version
*
* Take any SVG version 1.1 and transform
* child elements to 'path' elements
*
* This code is purely forked from
* https://github.com/Waest/SVGPathConverter
*/
/**
* Class constructor
*
* @param {DOM|String} element Dom element of the SVG or id of it
*/
function Pathformer(element) {
// Test params
if (typeof element === 'undefined') {
throw new Error('Pathformer [constructor]: "element" parameter is required');
}
// Set the element
if (element.constructor === String) {
element = document.getElementById(element);
if (!element) {
throw new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID');
}
}
if (element instanceof window.SVGElement ||
element instanceof window.SVGGElement ||
/^svg$/i.test(element.nodeName)) {
this.el = element;
} else {
throw new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement');
}
// Start
this.scan(element);
}
/**
* List of tags which can be transformed
* to path elements
*
* @type {Array}
*/
Pathformer.prototype.TYPES = ['line', 'ellipse', 'circle', 'polygon', 'polyline', 'rect'];
/**
* List of attribute names which contain
* data. This array list them to check if
* they contain bad values, like percentage.
*
* @type {Array}
*/
Pathformer.prototype.ATTR_WATCH = ['cx', 'cy', 'points', 'r', 'rx', 'ry', 'x', 'x1', 'x2', 'y', 'y1', 'y2'];
/**
* Finds the elements compatible for transform
* and apply the liked method
*
* @param {object} options Object from the constructor
*/
Pathformer.prototype.scan = function (svg) {
var fn, element, pathData, pathDom,
elements = svg.querySelectorAll(this.TYPES.join(','));
for (var i = 0; i < elements.length; i++) {
element = elements[i];
fn = this[element.tagName.toLowerCase() + 'ToPath'];
pathData = fn(this.parseAttr(element.attributes));
pathDom = this.pathMaker(element, pathData);
element.parentNode.replaceChild(pathDom, element);
}
};
/**
* Read `line` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element Line element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.lineToPath = function (element) {
var newElement = {},
x1 = element.x1 || 0,
y1 = element.y1 || 0,
x2 = element.x2 || 0,
y2 = element.y2 || 0;
newElement.d = 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
return newElement;
};
/**
* Read `rect` element to extract and transform
* data, to make it ready for a `path` object.
* The radius-border is not taken in charge yet.
* (your help is more than welcomed)
*
* @param {DOMelement} element Rect element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.rectToPath = function (element) {
var newElement = {},
x = parseFloat(element.x) || 0,
y = parseFloat(element.y) || 0,
width = parseFloat(element.width) || 0,
height = parseFloat(element.height) || 0;
if (element.rx || element.ry) {
var rx = parseInt(element.rx, 10) || -1,
ry = parseInt(element.ry, 10) || -1;
rx = Math.min(Math.max(rx < 0 ? ry : rx, 0), width/2);
ry = Math.min(Math.max(ry < 0 ? rx : ry, 0), height/2);
newElement.d = 'M ' + (x + rx) + ',' + y + ' ' +
'L ' + (x + width - rx) + ',' + y + ' ' +
'A ' + rx + ',' + ry + ',0,0,1,' + (x + width) + ',' + (y + ry) + ' ' +
'L ' + (x + width) + ',' + (y + height - ry) + ' ' +
'A ' + rx + ',' + ry + ',0,0,1,' + (x + width - rx) + ',' + (y + height) + ' ' +
'L ' + (x + rx) + ',' + (y + height) + ' ' +
'A ' + rx + ',' + ry + ',0,0,1,' + x + ',' + (y + height - ry) + ' ' +
'L ' + x + ',' + (y + ry) + ' ' +
'A ' + rx + ',' + ry + ',0,0,1,' + (x + rx) + ',' + y;
}
else {
newElement.d = 'M' + x + ' ' + y + ' ' +
'L' + (x + width) + ' ' + y + ' ' +
'L' + (x + width) + ' ' + (y + height) + ' ' +
'L' + x + ' ' + (y + height) + ' Z';
}
return newElement;
};
/**
* Read `polyline` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element Polyline element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.polylineToPath = function (element) {
var newElement = {},
points = element.points.trim().split(' '),
i, path;
// Reformatting if points are defined without commas
if (element.points.indexOf(',') === -1) {
var formattedPoints = [];
for (i = 0; i < points.length; i+=2) {
formattedPoints.push(points[i] + ',' + points[i+1]);
}
points = formattedPoints;
}
// Generate the path.d value
path = 'M' + points[0];
for(i = 1; i < points.length; i++) {
if (points[i].indexOf(',') !== -1) {
path += 'L' + points[i];
}
}
newElement.d = path;
return newElement;
};
/**
* Read `polygon` element to extract and transform
* data, to make it ready for a `path` object.
* This method rely on polylineToPath, because the
* logic is similar. The path created is just closed,
* so it needs an 'Z' at the end.
*
* @param {DOMelement} element Polygon element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.polygonToPath = function (element) {
var newElement = Pathformer.prototype.polylineToPath(element);
newElement.d += 'Z';
return newElement;
};
/**
* Read `ellipse` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element ellipse element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.ellipseToPath = function (element) {
var newElement = {},
rx = parseFloat(element.rx) || 0,
ry = parseFloat(element.ry) || 0,
cx = parseFloat(element.cx) || 0,
cy = parseFloat(element.cy) || 0,
startX = cx - rx,
startY = cy,
endX = parseFloat(cx) + parseFloat(rx),
endY = cy;
newElement.d = 'M' + startX + ',' + startY +
'A' + rx + ',' + ry + ' 0,1,1 ' + endX + ',' + endY +
'A' + rx + ',' + ry + ' 0,1,1 ' + startX + ',' + endY;
return newElement;
};
/**
* Read `circle` element to extract and transform
* data, to make it ready for a `path` object.
*
* @param {DOMelement} element Circle element to transform
* @return {object} Data for a `path` element
*/
Pathformer.prototype.circleToPath = function (element) {
var newElement = {},
r = parseFloat(element.r) || 0,
cx = parseFloat(element.cx) || 0,
cy = parseFloat(element.cy) || 0,
startX = cx - r,
startY = cy,
endX = parseFloat(cx) + parseFloat(r),
endY = cy;
newElement.d = 'M' + startX + ',' + startY +
'A' + r + ',' + r + ' 0,1,1 ' + endX + ',' + endY +
'A' + r + ',' + r + ' 0,1,1 ' + startX + ',' + endY;
return newElement;
};
/**
* Create `path` elements form original element
* and prepared objects
*
* @param {DOMelement} element Original element to transform
* @param {object} pathData Path data (from `toPath` methods)
* @return {DOMelement} Path element
*/
Pathformer.prototype.pathMaker = function (element, pathData) {
var i, attr, pathTag = document.createElementNS('http://www.w3.org/2000/svg','path');
for(i = 0; i < element.attributes.length; i++) {
attr = element.attributes[i];
if (this.ATTR_WATCH.indexOf(attr.name) === -1) {
pathTag.setAttribute(attr.name, attr.value);
}
}
for(i in pathData) {
pathTag.setAttribute(i, pathData[i]);
}
return pathTag;
};
/**
* Parse attributes of a DOM element to
* get an object of attribute => value
*
* @param {NamedNodeMap} attributes Attributes object from DOM element to parse
* @return {object} Object of attributes
*/
Pathformer.prototype.parseAttr = function (element) {
var attr, output = {};
for (var i = 0; i < element.length; i++) {
attr = element[i];
// Check if no data attribute contains '%', or the transformation is impossible
if (this.ATTR_WATCH.indexOf(attr.name) !== -1 && attr.value.indexOf('%') !== -1) {
throw new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.');
}
output[attr.name] = attr.value;
}
return output;
};

View File

@ -0,0 +1,917 @@
'use strict';
var setupEnv, requestAnimFrame, cancelAnimFrame, parsePositiveInt;
/**
* Vivus
* Beta version
*
* Take any SVG and make the animation
* to give give the impression of live drawing
*
* This in more than just inspired from codrops
* At that point, it's a pure fork.
*/
/**
* Class constructor
* option structure
* type: 'delayed'|'sync'|'oneByOne'|'script' (to know if the items must be drawn synchronously or not, default: delayed)
* duration: <int> (in frames)
* start: 'inViewport'|'manual'|'autostart' (start automatically the animation, default: inViewport)
* delay: <int> (delay between the drawing of first and last path)
* dashGap <integer> whitespace extra margin between dashes
* pathTimingFunction <function> timing animation function for each path element of the SVG
* animTimingFunction <function> timing animation function for the complete SVG
* forceRender <boolean> force the browser to re-render all updated path items
* selfDestroy <boolean> removes all extra styling on the SVG, and leaves it as original
*
* The attribute 'type' is by default on 'delayed'.
* - 'delayed'
* all paths are draw at the same time but with a
* little delay between them before start
* - 'sync'
* all path are start and finish at the same time
* - 'oneByOne'
* only one path is draw at the time
* the end of the first one will trigger the draw
* of the next one
*
* All these values can be overwritten individually
* for each path item in the SVG
* The value of frames will always take the advantage of
* the duration value.
* If you fail somewhere, an error will be thrown.
* Good luck.
*
* @constructor
* @this {Vivus}
* @param {DOM|String} element Dom element of the SVG or id of it
* @param {Object} options Options about the animation
* @param {Function} callback Callback for the end of the animation
*/
function Vivus(element, options, callback) {
setupEnv();
// Setup
this.isReady = false;
this.setElement(element, options);
this.setOptions(options);
this.setCallback(callback);
if (this.isReady) {
this.init();
}
}
/**
* Timing functions
**************************************
*
* Default functions to help developers.
* It always take a number as parameter (between 0 to 1) then
* return a number (between 0 and 1)
*/
Vivus.LINEAR = function(x) {
return x;
};
Vivus.EASE = function(x) {
return -Math.cos(x * Math.PI) / 2 + 0.5;
};
Vivus.EASE_OUT = function(x) {
return 1 - Math.pow(1 - x, 3);
};
Vivus.EASE_IN = function(x) {
return Math.pow(x, 3);
};
Vivus.EASE_OUT_BOUNCE = function(x) {
var base = -Math.cos(x * (0.5 * Math.PI)) + 1,
rate = Math.pow(base, 1.5),
rateR = Math.pow(1 - x, 2),
progress = -Math.abs(Math.cos(rate * (2.5 * Math.PI))) + 1;
return 1 - rateR + progress * rateR;
};
/**
* Setters
**************************************
*/
/**
* Check and set the element in the instance
* The method will not return anything, but will throw an
* error if the parameter is invalid
*
* @param {DOM|String} element SVG Dom element or id of it
*/
Vivus.prototype.setElement = function(element, options) {
var onLoad, self;
// Basic check
if (typeof element === 'undefined') {
throw new Error('Vivus [constructor]: "element" parameter is required');
}
// Set the element
if (element.constructor === String) {
element = document.getElementById(element);
if (!element) {
throw new Error(
'Vivus [constructor]: "element" parameter is not related to an existing ID'
);
}
}
this.parentEl = element;
// Load the SVG with XMLHttpRequest and extract the SVG
if (options && options.file) {
self = this;
onLoad = function() {
var domSandbox = document.createElement('div');
domSandbox.innerHTML = this.responseText;
var svgTag = domSandbox.querySelector('svg');
if (!svgTag) {
throw new Error(
'Vivus [load]: Cannot find the SVG in the loaded file : ' +
options.file
);
}
self.el = svgTag;
self.el.setAttribute('width', '100%');
self.el.setAttribute('height', '100%');
self.parentEl.appendChild(self.el);
self.isReady = true;
self.init();
self = null;
};
var oReq = new window.XMLHttpRequest();
oReq.addEventListener('load', onLoad);
oReq.open('GET', options.file);
oReq.send();
return;
}
switch (element.constructor) {
case window.SVGSVGElement:
case window.SVGElement:
case window.SVGGElement:
this.el = element;
this.isReady = true;
break;
case window.HTMLObjectElement:
self = this;
onLoad = function(e) {
if (self.isReady) {
return;
}
self.el =
element.contentDocument &&
element.contentDocument.querySelector('svg');
if (!self.el && e) {
throw new Error(
'Vivus [constructor]: object loaded does not contain any SVG'
);
} else if (self.el) {
if (element.getAttribute('built-by-vivus')) {
self.parentEl.insertBefore(self.el, element);
self.parentEl.removeChild(element);
self.el.setAttribute('width', '100%');
self.el.setAttribute('height', '100%');
}
self.isReady = true;
self.init();
self = null;
}
};
if (!onLoad()) {
element.addEventListener('load', onLoad);
}
break;
default:
throw new Error(
'Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'
);
}
};
/**
* Set up user option to the instance
* The method will not return anything, but will throw an
* error if the parameter is invalid
*
* @param {object} options Object from the constructor
*/
Vivus.prototype.setOptions = function(options) {
var allowedTypes = [
'delayed',
'sync',
'async',
'nsync',
'oneByOne',
'scenario',
'scenario-sync'
];
var allowedStarts = ['inViewport', 'manual', 'autostart'];
// Basic check
if (options !== undefined && options.constructor !== Object) {
throw new Error(
'Vivus [constructor]: "options" parameter must be an object'
);
} else {
options = options || {};
}
// Set the animation type
if (options.type && allowedTypes.indexOf(options.type) === -1) {
throw new Error(
'Vivus [constructor]: ' +
options.type +
' is not an existing animation `type`'
);
} else {
this.type = options.type || allowedTypes[0];
}
// Set the start type
if (options.start && allowedStarts.indexOf(options.start) === -1) {
throw new Error(
'Vivus [constructor]: ' +
options.start +
' is not an existing `start` option'
);
} else {
this.start = options.start || allowedStarts[0];
}
this.isIE =
window.navigator.userAgent.indexOf('MSIE') !== -1 ||
window.navigator.userAgent.indexOf('Trident/') !== -1 ||
window.navigator.userAgent.indexOf('Edge/') !== -1;
this.duration = parsePositiveInt(options.duration, 120);
this.delay = parsePositiveInt(options.delay, null);
this.dashGap = parsePositiveInt(options.dashGap, 1);
this.forceRender = options.hasOwnProperty('forceRender')
? !!options.forceRender
: this.isIE;
this.reverseStack = !!options.reverseStack;
this.selfDestroy = !!options.selfDestroy;
this.onReady = options.onReady;
this.map = [];
this.frameLength = this.currentFrame = this.delayUnit = this.speed = this.handle = null;
this.ignoreInvisible = options.hasOwnProperty('ignoreInvisible')
? !!options.ignoreInvisible
: false;
this.animTimingFunction = options.animTimingFunction || Vivus.LINEAR;
this.pathTimingFunction = options.pathTimingFunction || Vivus.LINEAR;
if (this.delay >= this.duration) {
throw new Error('Vivus [constructor]: delay must be shorter than duration');
}
};
/**
* Set up callback to the instance
* The method will not return enything, but will throw an
* error if the parameter is invalid
*
* @param {Function} callback Callback for the animation end
*/
Vivus.prototype.setCallback = function(callback) {
// Basic check
if (!!callback && callback.constructor !== Function) {
throw new Error(
'Vivus [constructor]: "callback" parameter must be a function'
);
}
this.callback = callback || function() {};
};
/**
* Core
**************************************
*/
/**
* Map the svg, path by path.
* The method return nothing, it just fill the
* `map` array. Each item in this array represent
* a path element from the SVG, with informations for
* the animation.
*
* ```
* [
* {
* el: <DOMobj> the path element
* length: <number> length of the path line
* startAt: <number> time start of the path animation (in frames)
* duration: <number> path animation duration (in frames)
* },
* ...
* ]
* ```
*
*/
Vivus.prototype.mapping = function() {
var i, paths, path, pAttrs, pathObj, totalLength, lengthMeter, timePoint, scale, hasNonScale;
timePoint = totalLength = lengthMeter = 0;
paths = this.el.querySelectorAll('path');
hasNonScale = false;
for (i = 0; i < paths.length; i++) {
path = paths[i];
if (this.isInvisible(path)) {
continue;
}
pathObj = {
el: path,
length: 0,
startAt: 0,
duration: 0,
isResizeSensitive: false
};
// If vector effect is non-scaling-stroke, the total length won't match the rendered length
// so we need to calculate the scale and apply it
if (path.getAttribute('vector-effect') === 'non-scaling-stroke') {
var rect = path.getBoundingClientRect();
var box = path.getBBox();
scale = Math.max(rect.width / box.width, rect.height / box.height);
pathObj.isResizeSensitive = true;
hasNonScale = true;
} else {
scale = 1;
}
pathObj.length = Math.ceil(path.getTotalLength() * scale);
// Test if the path length is correct
if (isNaN(pathObj.length)) {
if (window.console && console.warn) {
console.warn(
'Vivus [mapping]: cannot retrieve a path element length',
path
);
}
continue;
}
this.map.push(pathObj);
path.style.strokeDasharray =
pathObj.length + ' ' + (pathObj.length + this.dashGap * 2);
path.style.strokeDashoffset = pathObj.length + this.dashGap;
pathObj.length += this.dashGap;
totalLength += pathObj.length;
this.renderPath(i);
}
// Show a warning for non-scaling elements
if (hasNonScale) {
console.warn('Vivus: this SVG contains non-scaling-strokes. You should call instance.recalc() when the SVG is resized or you will encounter unwanted behaviour. See https://github.com/maxwellito/vivus#non-scaling for more info.');
}
totalLength = totalLength === 0 ? 1 : totalLength;
this.delay = this.delay === null ? this.duration / 3 : this.delay;
this.delayUnit = this.delay / (paths.length > 1 ? paths.length - 1 : 1);
// Reverse stack if asked
if (this.reverseStack) {
this.map.reverse();
}
for (i = 0; i < this.map.length; i++) {
pathObj = this.map[i];
switch (this.type) {
case 'delayed':
pathObj.startAt = this.delayUnit * i;
pathObj.duration = this.duration - this.delay;
break;
case 'oneByOne':
pathObj.startAt = (lengthMeter / totalLength) * this.duration;
pathObj.duration = (pathObj.length / totalLength) * this.duration;
break;
case 'sync':
case 'async':
case 'nsync':
pathObj.startAt = 0;
pathObj.duration = this.duration;
break;
case 'scenario-sync':
path = pathObj.el;
pAttrs = this.parseAttr(path);
pathObj.startAt =
timePoint +
(parsePositiveInt(pAttrs['data-delay'], this.delayUnit) || 0);
pathObj.duration = parsePositiveInt(
pAttrs['data-duration'],
this.duration
);
timePoint =
pAttrs['data-async'] !== undefined
? pathObj.startAt
: pathObj.startAt + pathObj.duration;
this.frameLength = Math.max(
this.frameLength,
pathObj.startAt + pathObj.duration
);
break;
case 'scenario':
path = pathObj.el;
pAttrs = this.parseAttr(path);
pathObj.startAt =
parsePositiveInt(pAttrs['data-start'], this.delayUnit) || 0;
pathObj.duration = parsePositiveInt(
pAttrs['data-duration'],
this.duration
);
this.frameLength = Math.max(
this.frameLength,
pathObj.startAt + pathObj.duration
);
break;
}
lengthMeter += pathObj.length;
this.frameLength = this.frameLength || this.duration;
}
};
/**
* Public method to re-evaluate line length for non-scaling lines
* path elements.
*/
Vivus.prototype.recalc = function () {
if (this.mustRecalcScale) {
return;
}
this.mustRecalcScale = requestAnimFrame(function () {
this.performLineRecalc();
}.bind(this));
}
/**
* Private method to re-evaluate line length on non-scaling
* path elements. Then call for a trace to update the SVG.
*/
Vivus.prototype.performLineRecalc = function () {
var pathObj, path, rect, box, scale;
for (var i = 0; i < this.map.length; i++) {
pathObj = this.map[i];
if (pathObj.isResizeSensitive) {
path = pathObj.el;
rect = path.getBoundingClientRect();
box = path.getBBox();
scale = Math.max(rect.width / box.width, rect.height / box.height);
pathObj.length = Math.ceil(path.getTotalLength() * scale);
path.style.strokeDasharray = pathObj.length + ' ' + (pathObj.length + this.dashGap * 2);
}
}
this.trace();
this.mustRecalcScale = null;
}
/**
* Interval method to draw the SVG from current
* position of the animation. It update the value of
* `currentFrame` and re-trace the SVG.
*
* It use this.handle to store the requestAnimationFrame
* and clear it one the animation is stopped. So this
* attribute can be used to know if the animation is
* playing.
*
* Once the animation at the end, this method will
* trigger the Vivus callback.
*
*/
Vivus.prototype.draw = function() {
var self = this;
this.currentFrame += this.speed;
if (this.currentFrame <= 0) {
this.stop();
this.reset();
} else if (this.currentFrame >= this.frameLength) {
this.stop();
this.currentFrame = this.frameLength;
this.trace();
if (this.selfDestroy) {
this.destroy();
}
} else {
this.trace();
this.handle = requestAnimFrame(function() {
self.draw();
});
return;
}
this.callback(this);
if (this.instanceCallback) {
this.instanceCallback(this);
this.instanceCallback = null;
}
};
/**
* Draw the SVG at the current instant from the
* `currentFrame` value. Here is where most of the magic is.
* The trick is to use the `strokeDashoffset` style property.
*
* For optimisation reasons, a new property called `progress`
* is added in each item of `map`. This one contain the current
* progress of the path element. Only if the new value is different
* the new value will be applied to the DOM element. This
* method save a lot of resources to re-render the SVG. And could
* be improved if the animation couldn't be played forward.
*
*/
Vivus.prototype.trace = function() {
var i, progress, path, currentFrame;
currentFrame =
this.animTimingFunction(this.currentFrame / this.frameLength) *
this.frameLength;
for (i = 0; i < this.map.length; i++) {
path = this.map[i];
progress = (currentFrame - path.startAt) / path.duration;
progress = this.pathTimingFunction(Math.max(0, Math.min(1, progress)));
if (path.progress !== progress) {
path.progress = progress;
path.el.style.strokeDashoffset = Math.floor(path.length * (1 - progress));
this.renderPath(i);
}
}
};
/**
* Method forcing the browser to re-render a path element
* from it's index in the map. Depending on the `forceRender`
* value.
* The trick is to replace the path element by it's clone.
* This practice is not recommended because it's asking more
* ressources, too much DOM manupulation..
* but it's the only way to let the magic happen on IE.
* By default, this fallback is only applied on IE.
*
* @param {Number} index Path index
*/
Vivus.prototype.renderPath = function(index) {
if (this.forceRender && this.map && this.map[index]) {
var pathObj = this.map[index],
newPath = pathObj.el.cloneNode(true);
pathObj.el.parentNode.replaceChild(newPath, pathObj.el);
pathObj.el = newPath;
}
};
/**
* When the SVG object is loaded and ready,
* this method will continue the initialisation.
*
* This this mainly due to the case of passing an
* object tag in the constructor. It will wait
* the end of the loading to initialise.
*
*/
Vivus.prototype.init = function() {
// Set object variables
this.frameLength = 0;
this.currentFrame = 0;
this.map = [];
// Start
new Pathformer(this.el);
this.mapping();
this.starter();
if (this.onReady) {
this.onReady(this);
}
};
/**
* Trigger to start of the animation.
* Depending on the `start` value, a different script
* will be applied.
*
* If the `start` value is not valid, an error will be thrown.
* Even if technically, this is impossible.
*
*/
Vivus.prototype.starter = function() {
switch (this.start) {
case 'manual':
return;
case 'autostart':
this.play();
break;
case 'inViewport':
var self = this,
listener = function() {
if (self.isInViewport(self.parentEl, 1)) {
self.play();
window.removeEventListener('scroll', listener);
}
};
window.addEventListener('scroll', listener);
listener();
break;
}
};
/**
* Controls
**************************************
*/
/**
* Get the current status of the animation between
* three different states: 'start', 'progress', 'end'.
* @return {string} Instance status
*/
Vivus.prototype.getStatus = function() {
return this.currentFrame === 0
? 'start'
: this.currentFrame === this.frameLength
? 'end'
: 'progress';
};
/**
* Reset the instance to the initial state : undraw
* Be careful, it just reset the animation, if you're
* playing the animation, this won't stop it. But just
* make it start from start.
*
*/
Vivus.prototype.reset = function() {
return this.setFrameProgress(0);
};
/**
* Set the instance to the final state : drawn
* Be careful, it just set the animation, if you're
* playing the animation on rewind, this won't stop it.
* But just make it start from the end.
*
*/
Vivus.prototype.finish = function() {
return this.setFrameProgress(1);
};
/**
* Set the level of progress of the drawing.
*
* @param {number} progress Level of progress to set
*/
Vivus.prototype.setFrameProgress = function(progress) {
progress = Math.min(1, Math.max(0, progress));
this.currentFrame = Math.round(this.frameLength * progress);
this.trace();
return this;
};
/**
* Play the animation at the desired speed.
* Speed must be a valid number (no zero).
* By default, the speed value is 1.
* But a negative value is accepted to go forward.
*
* And works with float too.
* But don't forget we are in JavaScript, se be nice
* with him and give him a 1/2^x value.
*
* @param {number} speed Animation speed [optional]
*/
Vivus.prototype.play = function(speed, callback) {
this.instanceCallback = null;
if (speed && typeof speed === 'function') {
this.instanceCallback = speed; // first parameter is actually the callback function
speed = null;
} else if (speed && typeof speed !== 'number') {
throw new Error('Vivus [play]: invalid speed');
}
// if the first parameter wasn't the callback, check if the seconds was
if (callback && typeof callback === 'function' && !this.instanceCallback) {
this.instanceCallback = callback;
}
this.speed = speed || 1;
if (!this.handle) {
this.draw();
}
return this;
};
/**
* Stop the current animation, if on progress.
* Should not trigger any error.
*
*/
Vivus.prototype.stop = function() {
if (this.handle) {
cancelAnimFrame(this.handle);
this.handle = null;
}
return this;
};
/**
* Destroy the instance.
* Remove all bad styling attributes on all
* path tags
*
*/
Vivus.prototype.destroy = function() {
this.stop();
var i, path;
for (i = 0; i < this.map.length; i++) {
path = this.map[i];
path.el.style.strokeDashoffset = null;
path.el.style.strokeDasharray = null;
this.renderPath(i);
}
};
/**
* Utils methods
* include methods from Codrops
**************************************
*/
/**
* Method to best guess if a path should added into
* the animation or not.
*
* 1. Use the `data-vivus-ignore` attribute if set
* 2. Check if the instance must ignore invisible paths
* 3. Check if the path is visible
*
* For now the visibility checking is unstable.
* It will be used for a beta phase.
*
* Other improvments are planned. Like detecting
* is the path got a stroke or a valid opacity.
*/
Vivus.prototype.isInvisible = function(el) {
var rect,
ignoreAttr = el.getAttribute('data-ignore');
if (ignoreAttr !== null) {
return ignoreAttr !== 'false';
}
if (this.ignoreInvisible) {
rect = el.getBoundingClientRect();
return !rect.width && !rect.height;
} else {
return false;
}
};
/**
* Parse attributes of a DOM element to
* get an object of {attributeName => attributeValue}
*
* @param {object} element DOM element to parse
* @return {object} Object of attributes
*/
Vivus.prototype.parseAttr = function(element) {
var attr,
output = {};
if (element && element.attributes) {
for (var i = 0; i < element.attributes.length; i++) {
attr = element.attributes[i];
output[attr.name] = attr.value;
}
}
return output;
};
/**
* Reply if an element is in the page viewport
*
* @param {object} el Element to observe
* @param {number} h Percentage of height
* @return {boolean}
*/
Vivus.prototype.isInViewport = function(el, h) {
var scrolled = this.scrollY(),
viewed = scrolled + this.getViewportH(),
elBCR = el.getBoundingClientRect(),
elHeight = elBCR.height,
elTop = scrolled + elBCR.top,
elBottom = elTop + elHeight;
// if 0, the element is considered in the viewport as soon as it enters.
// if 1, the element is considered in the viewport only when it's fully inside
// value in percentage (1 >= h >= 0)
h = h || 0;
return elTop + elHeight * h <= viewed && elBottom >= scrolled;
};
/**
* Get the viewport height in pixels
*
* @return {integer} Viewport height
*/
Vivus.prototype.getViewportH = function() {
var client = this.docElem.clientHeight,
inner = window.innerHeight;
if (client < inner) {
return inner;
} else {
return client;
}
};
/**
* Get the page Y offset
*
* @return {integer} Page Y offset
*/
Vivus.prototype.scrollY = function() {
return window.pageYOffset || this.docElem.scrollTop;
};
setupEnv = function() {
if (Vivus.prototype.docElem) {
return;
}
/**
* Alias for document element
*
* @type {DOMelement}
*/
Vivus.prototype.docElem = window.document.documentElement;
/**
* Alias for `requestAnimationFrame` or
* `setTimeout` function for deprecated browsers.
*
*/
requestAnimFrame = (function() {
return (
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(/* function */ callback) {
return window.setTimeout(callback, 1000 / 60);
}
);
})();
/**
* Alias for `cancelAnimationFrame` or
* `cancelTimeout` function for deprecated browsers.
*
*/
cancelAnimFrame = (function() {
return (
window.cancelAnimationFrame ||
window.webkitCancelAnimationFrame ||
window.mozCancelAnimationFrame ||
window.oCancelAnimationFrame ||
window.msCancelAnimationFrame ||
function(id) {
return window.clearTimeout(id);
}
);
})();
};
/**
* Parse string to integer.
* If the number is not positive or null
* the method will return the default value
* or 0 if undefined
*
* @param {string} value String to parse
* @param {*} defaultValue Value to return if the result parsed is invalid
* @return {number}
*
*/
parsePositiveInt = function(value, defaultValue) {
var output = parseInt(value, 10);
return output >= 0 ? output : defaultValue;
};

View File

@ -0,0 +1,75 @@
// Karma configuration
// Generated on Fri Jul 18 2014 10:58:08 GMT+0100 (BST)
module.exports = function(config) {
var options = {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '..',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
// list of files / patterns to load in the browser
files: [
'test/unit.setup.js',
'src/pathformer.js',
'src/vivus.js',
'test/unit/**.js'
],
// list of files to exclude
exclude: [],
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'../src/pathformer.js': ['coverage'],
'../src/vivus.js': ['coverage']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['progress', 'coverage'],
// optionally, configure the reporter
coverageReporter: {
type: 'html',
dir: '../coverage/'
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
};
if (process.env.TRAVIS) {
options.customLaunchers = {
Chrome_travis_ci: {
base: 'Chrome',
flags: ['--no-sandbox']
}
};
options.browsers = ['Chrome_travis_ci'];
}
config.set(options);
};

View File

@ -0,0 +1,30 @@
<svg height="300" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 404.7 354" enable-background="new 0 0 404.7 354">
<g stroke="#f9f9f9" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10">
<!-- HI -->
<path data-duration="10" d="M324.6,61.2c16.6,0,29.5-12.9,29.5-29.5c0-16.6-12.9-29.5-29.5-29.5c-16.6,0-29.5,12.9-29.5,29.5C295.1,48.4,308,61.2,324.6,61.2z"/>
<path data-duration="130" d="M366.2,204.2c-9.8,0-15-5.6-15-15.1V77.2h-85v28h19.5c9.8,0,8.5,2.1,8.5,11.6v72.4c0,9.5,0.5,15.1-9.3,15.1H277h-20.7c-8.5,0-14.2-4.1-14.2-12.9V52.4c0-8.5,5.7-12.3,14.2-12.3h18.8v-28h-127v28h18.1c8.5,0,9.9,2.1,9.9,8.9v56.1h-75V53.4c0-11.5,8.6-13.3,17-13.3h11v-28H2.2v28h26c8.5,0,12,2.1,12,7.9v142.2c0,8.5-3.6,13.9-12,13.9h-21v33h122v-33h-11c-8.5,0-17-4.1-17-12.2v-57.8h75v58.4c0,9.1-1.4,11.6-9.9,11.6h-18.1v33h122.9h5.9h102.2v-33H366.2z"/>
<path data-async="" data-delay="20" d="M358.8,82.8c11.1-4.2,18.8-14.7,18.8-27.5c0-8.5-3.4-16-8.9-21.3"/>
<path data-async="" d="M124.2,105.7V77c0-11.5,9.1-13.8,17.5-13.8h10.5V44.7"/>
<polyline data-async="" points="147.9,40.2 171.2,63.2 175.7,63.2"/>
<line data-async="" x1="295.1" y1="32.1" x2="275.2" y2="12.2"/>
<path data-async="" d="M266.2,204.7V75.9c0-8.5,5.2-12.8,13.7-12.8h18.3V44.7"/>
<polyline data-async="" points="265.9,105.2 289.2,129.2 293.7,129.2"/>
<polyline data-async="" points="374.2,204.7 374.2,94.2 358.8,82.8 351.2,77.2"/>
<polyline data-async="" points="148.2,237.2 171.2,261.2 294.6,261.2 300.5,261.2 402.2,261.2 402.2,228.2 379.2,204.2"/>
<polyline data-async="" points="124.2,204.7 124.2,157.2 175.7,157.2"/>
<line data-async="" x1="147.7" y1="228.2" x2="129.2" y2="204.2"/>
<polyline data-async="" points="7.2,237.3 30.2,261.2 152.2,261.2 152.2,241.7"/>
<polyline data-async="" points="1.9,40.2 26,63.2 39.7,63.2"/>
<line data-async="" x1="129.2" y1="12.2" x2="148.2" y2="33.2"/>
<line data-async="" x1="303.9" y1="53" x2="328.1" y2="77.2"/>
<line x1="345.1" y1="10.5" x2="368.7" y2="34"/>
<!-- there -->
<path data-delay="30" data-duration="60" stroke-linecap="round" stroke-linejoin="round" d="M76.8,337.3c0,0,1.9,12.2,13.1,12.2c22.1,0,23.8-1.8,59-66.4c-19.7,35.7-36.4,66.2-19.3,66.2c15.2,0,22.9-14.2,28.3-23.7c3.3-0.5,24-3.2,35-25.5c4-8.1,4.1-17.8-8.1-15.2c-5.6,1.2-13.1,14.8-15.7,19.2c-7.6,12.7-22.4,45.2-22.4,45.2s10.3-22.4,21.5-22.4c15.5,0-9.4,22.4,4.7,22.4c4.9,0,11.7-11.4,16.6-20.9c7.5,4.7,19.7,1.7,24.5-8.1c10.1-20.4-14.4-12.8-24.5,8.1c-5.5,11.3-2.2,21.1,11.2,21.1c16.4,0,26.1-28.3,30.5-37.5c9.9,2.5,14,2.5,22.7-1.1c-3.5,5.1-24,38.1-8.3,38.1c6.7,0,11.7-11.4,16.6-20.9c7.5,4.7,19.7,1.7,24.5-8.1c10.1-20.4-14.4-12.8-24.5,8.1c-5.5,11.3-2.2,21.1,11.2,21.1c16.4,0,20.6-4,24.7-10.5"/>
<path stroke-linecap="round" stroke-linejoin="round" d="M157.3,300.8c3.8-2.3-29,0.8-35.6,3.2"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -0,0 +1,425 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>vivus.js - manual tests page</title>
<meta name="description" content="SVG Drawing Animation" />
<style type="text/css">
/* Base style */
html {
font-size: 24px;
height: 100%;
}
body {
height: 100%;
margin: 0 0 40px;
font-family: 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;
font-weight: 200;
color: #666666;
background-color: #ffffff;
word-break: break-word;
}
h1,
h2,
h3,
h4,
h5,
h6,
p {
font-weight: 100;
}
a,
a:visited,
a:hover,
a:link {
color: inherit;
outline: 0;
}
small {
font-weight: 100;
}
p {
font-size: 1rem;
line-height: 1.4rem;
}
button,
.button {
margin: 0;
padding: 3px 6px;
border-radius: 6px;
border: 1px solid currentColor;
color: inherit;
background-color: rgba(0, 0, 0, 0);
font-size: 0.6rem;
font-weight: 300;
text-decoration: none;
cursor: pointer;
outline: 0;
}
button.active,
.button.active {
background-color: currentColor;
}
button.active span,
.button.active span {
color: #ffffff;
}
i {
background-color: rgba(0, 0, 0, 0.25);
border-radius: 4px;
}
svg * {
fill: none;
stroke: currentColor;
}
table {
border-collapse: collapse;
}
table,
th,
td {
border: 1px solid currentColor;
line-height: 0;
}
/* Layout */
.content {
margin: auto;
max-width: 960px;
width: 100%;
}
.box {
width: 100%;
display: inline-block;
vertical-align: top;
}
.section {
min-height: 90%;
padding: 20px;
box-sizing: border-box;
}
.warning {
padding: 0.5rem 0.75rem;
border: 1px solid currentColor;
color: #fff;
background-color: #c00;
border-radius: 0.25rem;
}
.hidden {
display: none;
}
@media (min-width: 768px) {
.box-50 {
width: 50%;
}
.section {
display: flex;
flex-direction: row;
}
}
/* Themes */
.intro {
display: block;
}
.sunrise {
color: #f037a5;
background-color: #f8c72c;
}
.matrix {
color: #86e0c4;
background-color: #181f21;
}
.electric {
color: #78c9db;
background-color: #e4175b;
}
.night {
color: #d3d679;
background-color: #316bd2;
}
</style>
</head>
<body>
<div class="section intro">
<h2>Vivus manual (cheap) tests.</h2>
<p>
Just scroll along the page and if a glitch appear or the visual
appearance is not like the description, it's not good.
</p>
<p id="config-instructions" class="warning hidden">
To use this page you must use an HTTP server to serve files. Run
<i>npm run serve</i> in the repository then go to the
<a href="http://127.0.0.1:8844/test/manual">test page</a>
</p>
</div>
<div class="section matrix">
<div class="content">
<div class="box box-50">
<p>
This should display the obturateur SVG like on the demo page. The
strokes must be orange. The element must remain an
<i>object</i> tag.
</p>
<button
onclick="this.textContent=(document.querySelector('object#obt')?'Pass!':'Failed.')"
>
Test
</button>
</div>
<div class="box box-50">
<object
id="obt"
data="obturateur.svg"
type="image/svg+xml"
style="width: 100%; max-height: 250px"
></object>
</div>
</div>
</div>
<div class="section sunrise">
<div class="content">
<div class="box box-50">
<div id="polaroid-dynamic"></div>
</div>
<div class="box box-50">
<p>
This should display the polaroid SVG like on the demo page. The
strokes must have the same color as this text.
</p>
</div>
</div>
</div>
<div class="section electric">
<div class="content">
<div class="box box-50">
<p>
This should display the 'Hi there' SVG like ready to start. Be sure
no glitch appear (no small path or dots). Click on the following
button to start.
</p>
<button onclick="hiD.play();">Start</button>
</div>
<div class="box box-50">
<div id="hi-dynamic" style="max-width: 300px; margin: auto"></div>
</div>
</div>
</div>
<div class="section night">
<div class="content">
<div class="box box-50">
<div id="synth-dynamic" style="max-width: 400px; margin: auto"></div>
</div>
<div class="box box-50">
<p>
This should display a synth ready to start. Be sure no glitch appear
(no small path or dots). The animation should use a custom path
timing function (ease_in: slow at start then finish fast.). Click on
the following button to start.
</p>
<button onclick="synthD.play();">Start</button>
</div>
</div>
</div>
<div class="section electric">
<div class="content">
<table id="nonScaling">
<tr style="height: 16.66%">
<td style="width: 16.66%">
<svg viewBox="0 0 100 100" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(.5,.5)"
/>
</svg>
</td>
<td style="width: 33.33%">
<svg viewBox="0 0 200 100" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(1,.5)"
/>
</svg>
</td>
<td style="width: 50%">
<svg viewBox="0 0 300 100" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(1.5,.5)"
/>
</svg>
</td>
</tr>
<tr style="height: 33.33%">
<td>
<svg viewBox="0 0 100 200" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(.5,1)"
/>
</svg>
</td>
<td>
<svg viewBox="0 0 200 200" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(1,1)"
/>
</svg>
</td>
<td>
<svg viewBox="0 0 300 200" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(1.5,1)"
/>
</svg>
</td>
</tr>
<tr style="height: 50%">
<td>
<svg viewBox="0 0 100 300" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(.5,1.5)"
/>
</svg>
</td>
<td>
<svg viewBox="0 0 200 300" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(1,1.5)"
/>
</svg>
</td>
<td>
<svg viewBox="0 0 300 300" width="100%" height="100%">
<line
vector-effect="non-scaling-stroke"
x1="0"
y1="0"
x2="200"
y2="200"
transform="scale(1.5,1.5)"
/>
</svg>
</td>
</tr>
</table>
<p>
Non scaling path<br />In any case of a resize, the animation of each
line must be complete.
</p>
<button onclick="nonScalingResize();">Resize</button>
<button onclick="nonScalingReplay();">Replay</button>
</div>
</div>
<!-- Le scripts -->
<script src="/dist/vivus.js"></script>
<script>
// Display warning message if not on http server
if (window.location.protocol === 'file:') {
var configIntro = document.getElementById('config-instructions');
configIntro.style.display = 'block';
}
// Obturateur
var obt1 = new Vivus('obt', {
type: 'delayed',
duration: 150,
});
// polaroid-dynamic
var polaroidD = new Vivus('polaroid-dynamic', {
file: 'polaroid.svg',
type: 'scenario-sync',
duration: 20,
});
var hiD = new Vivus('hi-dynamic', {
file: 'hi-there.svg',
type: 'scenario-sync',
duration: 20,
start: 'manual',
});
var synthD = new Vivus('synth-dynamic', {
file: 'synth.svg',
type: 'oneByOne',
duration: 200,
start: 'manual',
animTimingFunction: Vivus.EASE_IN,
});
// Non scaling
const resizeObserver = new ResizeObserver((entries) => {
nonScalingVivuses.forEach((v) => v.recalc());
});
resizeObserver.observe(window.nonScaling);
var nonScalingVivuses = Array.from(
document.querySelectorAll('#nonScaling svg')
).map((svg) => new Vivus(svg, { start: 'autostart' }));
function nonScalingResize() {
const newWidth = Math.floor(Math.random() * 100);
const newHeight = Math.floor(Math.random() * 100);
window.nonScaling.style.width = `${newWidth}%`;
window.nonScaling.style.height = `${newHeight}%`;
nonScalingVivuses.forEach((v) => v.recalc());
}
function nonScalingReplay() {
nonScalingVivuses.forEach((v) => v.reset().play());
}
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
width="100%" height="200px" viewBox="0 0 200 200" enable-background="new 0 0 200 200">
<g stroke="#f60" fill="none" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" >
<circle cx="100" cy="100" r="90"/>
<circle cx="100" cy="100" r="85.74"/>
<circle cx="100" cy="100" r="72.947"/>
<circle cx="100" cy="100" r="39.74"/>
<line x1="34.042" y1="131.189" x2="67.047" y2="77.781"/>
<line x1="31.306" y1="75.416" x2="92.41" y2="60.987"/>
<line x1="68.81" y1="34.042" x2="122.219" y2="67.046"/>
<line x1="124.584" y1="31.305" x2="139.013" y2="92.409"/>
<line x1="165.957" y1="68.809" x2="132.953" y2="122.219"/>
<line x1="168.693" y1="124.584" x2="107.59" y2="139.012"/>
<line x1="131.19" y1="165.957" x2="77.781" y2="132.953"/>
<line x1="75.417" y1="168.693" x2="60.987" y2="107.59"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 910 B

View File

@ -0,0 +1,62 @@
<svg id="polaroid" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px"
viewBox="0 0 200 160" enable-background="new 0 0 200 160">
<g stroke="#f9f9f9" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10">
<!-- Case -->
<!-- The case items will be drawn at the same time (attribute `data-async` on each tag) with a custom duration of 40 frames (attribute `data-duration`). WARNING: When you want to draw a bloc asynchronously (like here), the last item need to be `data-async` free. Otherwise the following tags will also start at the same time. I know it's a bit confusing, play a bit with it and you'll see. -->
<path data-async="" data-duration="40" d="
M106.725,104.742c-0.773,2.498-3.586,4.229-6.285,3.867L12.473,96.802c-2.699-0.363-4.262-2.682-3.49-5.18l25.164-81.436
c0.771-2.496,3.584-4.229,6.283-3.866l87.966,11.808c2.699,0.362,4.264,2.68,3.49,5.179L106.725,104.742z"/>
<path data-async="" data-duration="40" d="
M101.02,123.207c-0.773,2.5-3.587,4.23-6.286,3.867L6.766,115.27c-2.699-0.363-4.26-2.682-3.488-5.182l2.91-9.417
c0.771-2.499,3.585-4.23,6.285-3.87l87.967,11.809c2.699,0.361,4.261,2.682,3.49,5.18L101.02,123.207z"/>
<line data-async="" data-duration="40" x1="103.377" y1="128.225" x2="154.768" y2="155.32"/>
<line data-async="" data-duration="40" x1="109.852" y1="112.684" x2="155.035" y2="136.906"/>
<path data-async="" data-duration="40" d="
M9.096,120.207l47.932,21.994c0,0,98.06,12.414,97.74,13.119c-0.318,0.709,5.426-16.205,5.426-16.205l-2.646-96.842
c-1.098-7.587-2.467-11.8-8.559-15.024l-12.635-6.604"/>
<path data-async="" data-duration="40" d="
M161.586,38.135l30.717,16.085c0,0,5.295,2.323,4.543,6.504l-3.215,10.527c-0.648,2.621-2.939,4.988-8.229,2.798l-9.154-4.701
l-11.834,56.441"/>
<path data-duration="40" d="
M183.148,49.518c0,0,5.295,2.324,4.543,6.506l-3.215,10.526c-0.648,2.622-2.938,4.988-8.229,2.798"/>
<!-- Lens -->
<!-- All item will be drawn line by line, with an exception for the first one, a little delay (attribute `data-delay) to make a break between the drawing of the case and the start of the lens part -->
<path data-delay="20" d="
M87.176,56.143C83.274,68.78,69.043,77.538,55.395,75.706S33.846,62.146,37.75,49.511c3.903-12.637,18.135-21.392,31.783-19.562
C83.181,31.782,91.081,43.51,87.176,56.143z"/>
<path d="
M92.745,56.891c-4.785,15.48-22.219,26.213-38.942,23.969C37.079,78.615,27.4,64.245,32.184,48.763
c4.783-15.48,22.218-26.211,38.94-23.968C87.848,27.041,97.528,41.411,92.745,56.891z"/>
<path d="
M78.99,26.933c16.169,7.426,19.398,10.989,22.026,20.105c1.283,4.449,1.271,9.411-0.3,14.489
c-4.783,15.479-22.217,26.213-38.941,23.969c-8.68-1.165-21.171-7.963-25.613-14.055"/>
<path d="
M42.602,50.162c3.137-10.157,14.573-17.193,25.543-15.722"/>
<!-- Flash -->
<!-- This tag does not have any extra attribute. So it will start when the previous tag is finished. His duration and delay will be the one given in the options. -->
<path d="
M103.789,29.275c-0.568,1.841,0.582,3.549,2.57,3.818l12.807,1.72c1.988,0.266,4.062-1.012,4.633-2.851l1.66-5.38
c0.568-1.843-0.582-3.551-2.57-3.816l-12.807-1.72c-1.988-0.268-4.062,1.01-4.633,2.85L103.789,29.275z"/>
<!-- Output -->
<!-- Same case as Flash -->
<path d="
M11.129,105.791c-0.297,0.965,0.305,1.855,1.346,1.994l81.446,10.932c1.038,0.141,2.123-0.527,2.42-1.49l0,0
c0.298-0.961-0.304-1.855-1.343-1.996l-81.447-10.93C12.51,104.16,11.426,104.828,11.129,105.791L11.129,105.791z"/>
<!-- Design (color lines on the front) -->
<!-- All the lines will start at the same time, because they all have the attribute `data-async` -->
<line data-async="" x1="47.583" y1="101.505" x2="51.561" y2="88.267"/>
<line data-async="" x1="53.391" y1="102.326" x2="57.047" y2="90.125"/>
<line data-async="" x1="59.224" y1="103.068" x2="62.749" y2="91.295"/>
<line data-async="" x1="65.057" y1="103.814" x2="69.015" y2="90.637"/>
<line data-async="" x1="72.87" y1="19.969" x2="75.497" y2="11.082"/>
<line data-async="" x1="78.512" y1="21.325" x2="81.317" y2="11.868"/>
<line data-async="" x1="83.833" y1="23.718" x2="87.16" y2="12.582"/>
<line data-async="" x1="89.145" y1="26.141" x2="92.939" y2="13.498"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="378.995px" height="259.5px" viewBox="0 0 378.995 259.5" enable-background="new 0 0 378.995 259.5" xml:space="preserve">
<g id="Synth">
<path fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M375.5,108.934c1.549,2.693,0.627,5.739-2.059,6.804L72.043,235.257c-2.685,1.064-6.116-0.254-7.665-2.946L5.362,129.69
c-1.548-2.692-0.625-5.737,2.059-6.802L308.818,3.369c2.686-1.065,6.117,0.254,7.666,2.946L375.5,108.934z"/>
<path fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" d="
M376.246,111.47l-2.068,18.344c0,0-0.621,5.361-4.932,7.726L69.601,256.365c-2.685,1.064-6.116-0.254-7.665-2.946L3.693,152.145
c-1.548-2.692-0.878-9.891-0.878-9.891l0.82-7.014"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="373.785" y1="112.765" x2="371.715" y2="130.65"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="70.318" y1="250.17" x2="371.715" y2="130.65"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="319.578" y1="22.078" x2="19.852" y2="140.935"/>
<polyline fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
19.852,140.935 72.387,232.284 70.318,250.17 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="371.715" y1="130.65" x2="365.053" y2="119.063"/>
<g id="octaves_1_">
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="93.572" y1="226.72" x2="73.09" y2="191.106"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="114.616" y1="218.126" x2="94.134" y2="182.512"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="136.629" y1="209.646" x2="82.765" y2="115.986"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="158.158" y1="201.108" x2="137.674" y2="165.493"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="179.688" y1="192.572" x2="159.203" y2="156.957"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="201.213" y1="184.034" x2="180.732" y2="148.419"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="222.742" y1="175.497" x2="168.879" y2="81.838"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="244.27" y1="166.959" x2="223.789" y2="131.346"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="265.799" y1="158.423" x2="245.318" y2="122.809"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="287.328" y1="149.886" x2="233.463" y2="56.226"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="308.855" y1="141.349" x2="288.375" y2="105.734"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="330.385" y1="132.812" x2="309.902" y2="97.197"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="351.912" y1="124.274" x2="331.432" y2="88.66"/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="373.441" y1="115.737" x2="319.578" y2="22.078"/>
</g>
<g id="bemols_1_">
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
67.427,190.815 35.241,134.85 39.708,133.061 44.176,131.271 76.24,177.929 76.362,187.236 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
69.3,180.708 36.616,135.539 44.606,132.34 76.24,177.929 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="69.077" y1="183.09" x2="68.222" y2="187.702"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
89.117,182.213 56.931,126.247 61.398,124.458 65.866,122.669 97.93,169.326 98.052,178.634 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
90.99,172.105 58.306,126.937 66.295,123.736 97.93,169.326 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="90.767" y1="174.487" x2="89.912" y2="179.1"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
132.012,165.204 99.826,109.238 104.293,107.449 108.762,105.661 140.825,152.317 140.948,161.625 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
133.885,155.098 101.202,109.928 109.191,106.728 140.825,152.317 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="133.661" y1="157.479" x2="132.807" y2="162.091"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
153.541,156.666 121.354,100.7 125.821,98.911 130.289,97.122 162.354,143.779 162.475,153.088 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
155.416,146.559 122.729,101.39 130.719,98.19 162.354,143.779 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="155.191" y1="148.94" x2="154.335" y2="153.554"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
175.068,148.129 142.881,92.164 147.348,90.374 151.817,88.585 183.881,135.242 184.004,144.551 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
176.941,138.021 144.256,92.853 152.247,89.653 183.881,135.242 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="176.719" y1="140.403" x2="175.863" y2="145.017"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
218.127,131.056 185.939,75.089 190.406,73.3 194.875,71.512 226.938,118.169 227.061,127.476 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
220,120.948 187.314,75.778 195.305,72.579 226.938,118.169 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="219.775" y1="123.329" x2="218.922" y2="127.942"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
239.656,122.518 207.469,66.553 211.936,64.763 216.402,62.975 248.467,109.631 248.59,118.939 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
241.529,112.411 208.844,67.241 216.834,64.042 248.467,109.631 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="241.305" y1="114.792" x2="240.449" y2="119.405"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
282.713,105.443 250.525,49.478 254.992,47.688 259.459,45.9 291.523,92.558 291.646,101.864 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
284.584,95.337 251.902,50.168 259.891,46.968 291.523,92.558 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="284.361" y1="97.718" x2="283.508" y2="102.33"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
304.24,96.906 272.055,40.941 276.52,39.151 280.988,37.363 313.053,84.02 313.174,93.328 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
306.113,86.8 273.43,41.631 281.42,38.431 313.053,84.02 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="305.891" y1="89.181" x2="305.035" y2="93.794"/>
</g>
<g>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
325.768,88.369 293.582,32.404 298.049,30.614 302.518,28.825 334.58,75.482 334.703,84.791 "/>
<polygon fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" points="
327.641,78.262 294.957,33.093 302.947,29.894 334.58,75.482 "/>
<line fill="none" stroke="#241F20" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="10" x1="327.418" y1="80.644" x2="326.562" y2="85.257"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,14 @@
/* Here is a cheap and bad implementation
* of requestAnimationFrame and
* cancelAnimationFrame mock.
* But it's more than enough
* for our tests.
*/
window.requestAnimFrameStack = [];
window.requestAnimationFrame = function (callback) {
window.requestAnimFrameStack.push(callback);
return true;
};
window.cancelAnimationFrame = function () {
window.requestAnimFrameStack = [];
};

View File

@ -0,0 +1,322 @@
'use strict';
/**
* Unit tests for Pathformer
*
*/
describe('Pathformer', function () {
var svgTag,
svgTagId = 'my-svg',
svgGroupTag,
svgGroupTagId = 'my-svg-group';
beforeEach(function () {
// Remove tag if existing
svgTag = document.getElementById(svgTagId);
if (svgTag) {
svgTag.remove();
}
// Create the SVG
svgTag = document.createElementNS('http://www.w3.org/2000/svg','svg');
svgTag.id = svgTagId;
svgTag.innerHTML = '<circle fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" cx="100" cy="100" r="72.947"/>' +
'<circle fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" cx="100" cy="100" r="39.74"/>' +
'<g id="' + svgGroupTagId + '">' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="34.042" y1="131.189" x2="67.047" y2="77.781"/>' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="165.957" y1="68.809" x2="132.953" y2="122.219"/>' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="131.19" y1="165.957" x2="77.781" y2="132.953"/>' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="68.81" y1="34.042" x2="122.219" y2="67.046"/>' +
'</g>';
svgGroupTag = svgTag.querySelector('#'+svgGroupTagId);
// Insert it to the body
document.body.appendChild(svgTag);
});
describe('[param tests]', function () {
// Tests about the SVG element
it('should throw an error if the SVG is given in parameter', function () {
expect(function () {
new Pathformer();
}).toThrow(new Error('Pathformer [constructor]: "element" parameter is required'));
});
it('should work with only the SVG id', function () {
expect(function () {
new Pathformer(svgTagId);
}).not.toThrow();
});
it('should work with only the SVG object', function () {
expect(function () {
new Pathformer(svgTag);
}).not.toThrow();
});
it('should work with only the SVG group object', function () {
expect(function () {
new Pathformer(svgGroupTag);
}).not.toThrow();
});
it('should throw an error if the SVG ID given is invalid', function () {
expect(function () {
new Pathformer('my-unexisting-svg');
}).toThrow(new Error('Pathformer [constructor]: "element" parameter is not related to an existing ID'));
});
it('should throw an error if the ID given is not related to a SVG element', function () {
var divTag = document.createElement('div');
divTag.id = 'my-div';
document.body.appendChild(divTag);
expect(function () {
new Pathformer('my-div');
}).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
});
it('should throw an error if the element is not a correct type (DOM object or string)', function () {
expect(function () { new Pathformer({}); }).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
expect(function () { new Pathformer(42); }).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
expect(function () { new Pathformer(false); }).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
expect(function () { new Pathformer(new Date()); }).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
expect(function () { new Pathformer(function () {}); }).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
expect(function () { new Pathformer(document.createElement('div')); }).toThrow(new Error('Pathformer [constructor]: "element" parameter must be a string or a SVGelement'));
});
});
describe('[translation]', function () {
// Line object
describe('line', function () {
it('should return an object with a `d` attribute', function () {
var output = Pathformer.prototype.lineToPath({});
expect(output.d).toBeDefined();
});
it('should return an object with an unclosed shape', function () {
var output = Pathformer.prototype.lineToPath({});
expect(output.d.substr(-1)).not.toEqual('Z');
});
it('should set default positino attributes to zero', function () {
var output = Pathformer.prototype.lineToPath({
x1: '21', x2: '32', y1: '11'
});
expect(output.d.indexOf('0')).not.toEqual(-1);
expect(output.d.indexOf('undefined')).toEqual(-1);
});
});
// Rect object
describe('rect', function () {
it('should return an object with a `d` attribute', function () {
var output = Pathformer.prototype.rectToPath({});
expect(output.d).toBeDefined();
});
it('should return an object with a closed shape', function () {
var output = Pathformer.prototype.rectToPath({});
expect(output.d.substr(-1)).toEqual('Z');
});
it('should set default positino attributes to zero', function () {
var output = Pathformer.prototype.rectToPath({
x: '21', height: '32', width: '11'
});
expect(output.d.indexOf('0')).not.toEqual(-1);
expect(output.d.indexOf('undefined')).toEqual(-1);
});
it('should apply rounded corners', function () {
var result = 'M 50,10 ' +
'L 50,10 A 40,20,0,0,1,90,30 ' +
'L 90,50 A 40,20,0,0,1,50,70 ' +
'L 50,70 A 40,20,0,0,1,10,50 ' +
'L 10,30 A 40,20,0,0,1,50,10';
var output = Pathformer.prototype.rectToPath({
x:10, y:10, width:80, height:60, rx:100, ry:20
});
expect(output.d).toEqual(result);
});
it('should apply rounded corners even when a value is missing', function () {
var result = 'M 30,10 ' +
'L 70,10 A 20,20,0,0,1,90,30 ' +
'L 90,50 A 20,20,0,0,1,70,70 ' +
'L 30,70 A 20,20,0,0,1,10,50 ' +
'L 10,30 A 20,20,0,0,1,30,10';
var output = Pathformer.prototype.rectToPath({
x:10, y:10, width:80, height:60, ry:20
});
expect(output.d).toEqual(result);
});
});
// Polyline object
describe('polyline', function () {
var polyline;
beforeEach(function () {
polyline = {
points: '2,3 4,5 6,7'
};
});
it('should return an object with a `d` attribute', function () {
var output = Pathformer.prototype.polylineToPath(polyline);
expect(output.d).toBeDefined();
});
it('should return an object with an unclosed shape', function () {
var output = Pathformer.prototype.polylineToPath(polyline);
expect(output.d.substr(-1)).not.toEqual('Z');
});
it('should ignore incorrect points', function () {
var output;
polyline.points += ' 43';
output = Pathformer.prototype.polylineToPath(polyline);
expect(output.d.indexOf('43')).toEqual(-1);
});
it('should accept points defined with and without commas', function () {
var outputWithPoint = Pathformer.prototype.polylineToPath(polyline);
var outputWithoutPoint = Pathformer.prototype.polylineToPath({points: '2 3 4 5 6 7'});
expect(outputWithPoint).toEqual(outputWithoutPoint);
});
});
// Polygon object
describe('polygon', function () {
var polygon;
beforeEach(function () {
polygon = {
points: '2,3 4,5 6,7'
};
});
it('should return an object with a `d` attribute', function () {
var output = Pathformer.prototype.polygonToPath(polygon);
expect(output.d).toBeDefined();
});
it('should return an object with a closed shape', function () {
var output = Pathformer.prototype.polygonToPath(polygon);
expect(output.d.substr(-1)).toEqual('Z');
});
});
// Ellipse object
describe('ellipse', function () {
var ellipse;
beforeEach(function () {
ellipse = {
cx: 2,
cy: 3,
rx: 3
};
});
it('should return an object with a `d` attribute', function () {
var output = Pathformer.prototype.ellipseToPath(ellipse);
expect(output.d).toBeDefined();
});
it('should return an object with an unclosed shape', function () {
var output = Pathformer.prototype.ellipseToPath(ellipse);
expect(output.d.substr(-1)).not.toEqual('Z');
});
it('should set default positino attributes to zero', function () {
delete ellipse.cy;
var output = Pathformer.prototype.ellipseToPath(ellipse);
expect(output.d.indexOf('0')).not.toEqual(-1);
expect(output.d.indexOf('undefined')).toEqual(-1);
});
});
// Circle object
describe('circle', function () {
var circle;
beforeEach(function () {
circle = {
cx: 2,
cy: 3,
rx: 3,
r: 1
};
});
it('should return an object with a `d` attribute', function () {
var output = Pathformer.prototype.circleToPath(circle);
expect(output.d).toBeDefined();
});
it('should return an object with an unclosed shape', function () {
var output = Pathformer.prototype.circleToPath(circle);
expect(output.d.substr(-1)).not.toEqual('Z');
});
it('should set default positino attributes to zero', function () {
delete circle.cy;
var output = Pathformer.prototype.circleToPath(circle);
expect(output.d.indexOf('0')).not.toEqual(-1);
expect(output.d.indexOf('undefined')).toEqual(-1);
});
});
});
describe('[utils]', function () {
describe('attribute parser', function () {
it('should return an empty object if attributes length are undefined', function () {
var output = Pathformer.prototype.parseAttr({});
expect(output).toEqual({});
});
});
describe('engine', function () {
it('shouldn\'t throw an error if the SVG got a tag not taken in charge', function () {
svgTag.innerHTML = '<polypentagoneofhell fill="none" stroke="#666666" stroke-width="6" stroke-miterlimit="666" cx="666" cy="666"/>';
expect(function () {
new Pathformer(svgTagId);
}).not.toThrow();
});
it('should remove useless attributes during transformation', function () {
new Pathformer(svgTagId);
expect(svgTag.childNodes[0].getAttribute('cx')).toBe(null);
});
});
describe('validity', function () {
it('should throw error if the SVG contain shape with percentage value', function () {
// Create the SVG
var svgTagPrc = document.createElementNS('http://www.w3.org/2000/svg','svg');
svgTagPrc.innerHTML = '<circle cx="100%" cy="100" r="10"/>';
expect(function () {
new Pathformer(svgTagPrc);
}).toThrow(new Error('Pathformer [parseAttr]: a SVG shape got values in percentage. This cannot be transformed into \'path\' tags. Please use \'viewBox\'.'));
});
it('shouldn\'t throw error if the SVG contain shape with percentage value on a non-data attribute', function () {
// Create the SVG
var svgTagPrc = document.createElementNS('http://www.w3.org/2000/svg','svg');
svgTagPrc.innerHTML = '<circle width="100%" cx="100" cy="100" r="10"/>';
expect(function () {
new Pathformer(svgTagPrc);
}).not.toThrow();
});
});
});
});

View File

@ -0,0 +1,669 @@
'use strict';
/**
* Unit tests for Vivus
*
*/
describe('Vivus', function () {
var ObjectElementMock,
triggerFrames,
myVivus,
objTag,
wrapTag,
svgTag,
svgTagId = 'my-svg',
svgGroupTagId = 'my-svg-group';
// Mock ObjectElement and it's constructor via createElement
ObjectElementMock = function () {
this.loadCb = [];
this.attr = {};
this.addEventListener = function (evtName, cb) {
if (evtName === 'load') {
this.loadCb.push(cb);
}
};
this.loaded = function () {
for (var i = 0; i < this.loadCb.length; i++) {
this.loadCb[i]({target: this});
}
};
this.getBoundingClientRect = function () {
return {
height: 11,
top: 364
};
};
this.insertBefore = function () {};
this.removeChild = function () {};
this.setAttribute = function (key, val) {
this.attr[key] = val;
};
this.getAttribute = function (key) {
return this.attr[key];
};
};
window.HTMLObjectElement = ObjectElementMock;
triggerFrames = function (counter) {
counter = counter || -1;
while (window.requestAnimFrameStack.length && counter !== 0) {
window.requestAnimFrameStack.shift()();
counter--;
}
};
beforeEach(function () {
// Create the SVG
svgTag = document.createElementNS('http://www.w3.org/2000/svg','svg');
svgTag.id = svgTagId;
svgTag.innerHTML = '<circle fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" cx="100" cy="100" r="72.947"/>' +
'<circle fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" cx="100" cy="100" r="39.74"/>' +
'<g id="' + svgGroupTagId + '">' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="34.042" y1="131.189" x2="67.047" y2="77.781"/>' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="165.957" y1="68.809" x2="132.953" y2="122.219"/>' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="131.19" y1="165.957" x2="77.781" y2="132.953"/>' +
'<line fill="none" stroke="#f9f9f9" stroke-width="3" stroke-miterlimit="10" x1="68.81" y1="34.042" x2="122.219" y2="67.046"/>' +
'</g>';
wrapTag = document.createElement('div');
wrapTag.appendChild(svgTag);
document.body.appendChild(wrapTag);
// Reset the request anim frame stack
window.requestAnimFrameStack = [];
});
afterEach(function () {
// Remove tag
svgTag.remove();
wrapTag.remove();
});
describe('[basic tests]', function () {
it('should the class be defined under Vivus name', function () {
expect(Vivus).toBeDefined();
});
it('should have timing functions set', function () {
expect(Vivus.LINEAR).toBeDefined();
expect(Vivus.EASE).toBeDefined();
expect(Vivus.EASE_IN).toBeDefined();
expect(Vivus.EASE_OUT).toBeDefined();
expect(Vivus.EASE_OUT_BOUNCE).toBeDefined();
});
it('should have timing functions returning correct value on limits', function () {
expect(Vivus.LINEAR(0)).toEqual(0);
expect(Vivus.LINEAR(1)).toEqual(1);
expect(Vivus.EASE(0)).toEqual(0);
expect(Vivus.EASE(1)).toEqual(1);
expect(Vivus.EASE_IN(0)).toEqual(0);
expect(Vivus.EASE_IN(1)).toEqual(1);
expect(Vivus.EASE_OUT(0)).toEqual(0);
expect(Vivus.EASE_OUT(1)).toEqual(1);
expect(Vivus.EASE_OUT_BOUNCE(0)).toEqual(0);
expect(Vivus.EASE_OUT_BOUNCE(1)).toEqual(1);
});
});
describe('[param tests]', function () {
// Tests about the SVG element
it('should throw an error if the SVG is given in parameter', function () {
expect(function () {
new Vivus();
}).toThrow(new Error('Vivus [constructor]: "element" parameter is required'));
});
it('should work with only the SVG id', function () {
expect(function () {
new Vivus(svgTagId);
}).not.toThrow();
});
it('should work with only the SVG object', function () {
expect(function () {
new Vivus(svgTag);
}).not.toThrow();
});
it('should work with the SVG group object', function () {
expect(function () {
new Vivus(svgGroupTagId);
}).not.toThrow();
});
it('should throw an error if the SVG ID given is invalid', function () {
expect(function () {
new Vivus('my-unexisting-svg');
}).toThrow(new Error('Vivus [constructor]: "element" parameter is not related to an existing ID'));
});
it('should throw an error if the ID given is not related to a SVG element', function () {
var divTag = document.createElement('div');
divTag.id = 'my-div';
document.body.appendChild(divTag);
expect(function () {
new Vivus('my-div');
}).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
});
it('should accept any DOM element if `file` option is set', function () {
var divTag = document.createElement('div');
spyOn(window, 'XMLHttpRequest');
try {
new Vivus(divTag, {file: 'opensource.svg'});
}
catch(err) {}
expect(window.XMLHttpRequest).toHaveBeenCalled();
});
it('should throw an error if the element is not a correct type (DOM object or string)', function () {
expect(function () { new Vivus({}); }).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
expect(function () { new Vivus(42); }).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
expect(function () { new Vivus(false); }).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
expect(function () { new Vivus(new Date()); }).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
expect(function () { new Vivus(function () {}); }).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
expect(function () { new Vivus(document.createElement('div')); }).toThrow(new Error('Vivus [constructor]: "element" parameter is not valid (or miss the "file" attribute)'));
});
it('should accept object element', function () {
// Create a mock Object getElementById
objTag = new ObjectElementMock();
objTag.contentDocument = wrapTag;
expect(function () {
new Vivus(objTag);
}).not.toThrow();
});
it('the vivus state should be ready if the SVG is already loaded', function () {
objTag = new ObjectElementMock();
objTag.contentDocument = wrapTag;
objTag.loaded();
var myVivus = new Vivus(objTag);
expect(myVivus.isReady).toEqual(true);
});
it('the vivus instance should have `el` and `parentEl` different if the element is an object', function () {
objTag = new ObjectElementMock();
objTag.contentDocument = wrapTag;
objTag.loaded();
var myVivus = new Vivus(objTag);
expect(myVivus.parentEl).not.toEqual(myVivus.el);
});
it('should call `onReady` callback once the SVG is loaded', function () {
objTag = new ObjectElementMock();
objTag.contentDocument = document.createElement('div');
var myVivus = new Vivus(objTag);
objTag.contentDocument = wrapTag;
objTag.loaded();
expect(myVivus.isReady).toEqual(true);
});
it('should throw an error if the SVG file does not exists', function () {
objTag = new ObjectElementMock();
objTag.contentDocument = document.createElement('div');
new Vivus(objTag);
expect(function () {
objTag.loaded();
}).toThrow();
});
// Options
it('should work without options', function () {
expect(function () {
new Vivus(svgTag);
}).not.toThrow();
});
it('should throw an error if options is not an object', function () {
expect(function () { new Vivus(svgTag, []); }).toThrow(new Error('Vivus [constructor]: "options" parameter must be an object'));
expect(function () { new Vivus(svgTag, 42); }).toThrow(new Error('Vivus [constructor]: "options" parameter must be an object'));
expect(function () { new Vivus(svgTag, false); }).toThrow(new Error('Vivus [constructor]: "options" parameter must be an object'));
expect(function () { new Vivus(svgTag, new Date()); }).toThrow(new Error('Vivus [constructor]: "options" parameter must be an object'));
expect(function () { new Vivus(svgTag, 'manual'); }).toThrow(new Error('Vivus [constructor]: "options" parameter must be an object'));
expect(function () { new Vivus(svgTag, function () {}); }).toThrow(new Error('Vivus [constructor]: "options" parameter must be an object'));
});
// Options
it('should work with empty option object', function () {
expect(function () {
new Vivus(svgTag, {});
}).not.toThrow();
});
it('should throw an error if the `type` value given in options does not exists', function () {
expect(function () {
new Vivus(svgTag, {type: 'by-unicorn'});
}).toThrow(new Error('Vivus [constructor]: by-unicorn is not an existing animation `type`'));
});
it('should throw an error if the `start` value given in options is not a string', function () {
expect(function () {
new Vivus(svgTag, {start: 'when-unicorn-ready'});
}).toThrow(new Error('Vivus [constructor]: when-unicorn-ready is not an existing `start` option'));
});
it('should throw an error if the `delay` value is bigger (or equal) than `duration`', function () {
expect(function () {
new Vivus(svgTag, {duration: 200, delay: 199});
}).not.toThrow();
expect(function () {
new Vivus(svgTag, {duration: 200, delay: 200});
}).toThrow(new Error('Vivus [constructor]: delay must be shorter than duration'));
expect(function () {
new Vivus(svgTag, {duration: 200, delay: 201});
}).toThrow(new Error('Vivus [constructor]: delay must be shorter than duration'));
});
it('should override `duration` if invalid', function () {
myVivus = new Vivus(svgTag, {duration: -12});
expect(myVivus.duration > 0).toBe(true);
});
it('should override `delay` if invalid, with a null value', function () {
myVivus = new Vivus(svgTag, {delay: -12});
expect(!myVivus.delay).toBe(false);
});
it('should set up default values', function () {
myVivus = new Vivus(svgTag, {});
expect(myVivus.type).toBeDefined();
expect(myVivus.start).toBeDefined();
expect(myVivus.duration).toBeDefined();
});
it('the vivus instance should have `el` and `parentEl` equal if the element is a SVG object', function () {
myVivus = new Vivus(svgTag, {});
expect(myVivus.el).toEqual(myVivus.parentEl);
});
// Callback
it('should throw an error if callback is non a function', function () {
expect(function () {
new Vivus(svgTag, {}, 42);
}).toThrow(new Error('Vivus [constructor]: "callback" parameter must be a function'));
});
it('should use scale to determine path length when vector effect is non-scaling-stroke', function () {
var scalingSvgTag = document.createElementNS('http://www.w3.org/2000/svg','svg');
var scalingWrapTag = document.createElement('div');
scalingSvgTag.setAttribute('viewBox', '0 0 500 200');
scalingWrapTag.style.width = '1000px';
scalingSvgTag.id = 'scaling-stroke-test';
scalingSvgTag.innerHTML = '<path vector-effect="non-scaling-stroke" fill="none" stroke="#f9f9f9" stroke-width="3" d="M0,68.57346635098205L20.833333333333332,3.8875909891199285L41.666666666666664,47.366000806779425L62.5,57.171841641625065L83.33333333333333"/>' +
'<path fill="none" stroke="#f9f9f9" stroke-width="3" d="M0,68.57346635098205L20.833333333333332,3.8875909891199285L41.666666666666664,47.366000806779425L62.5,57.171841641625065L83.33333333333333"/>';
scalingWrapTag.appendChild(scalingSvgTag);
document.body.appendChild(scalingWrapTag);
myVivus = new Vivus(scalingSvgTag);
expect(myVivus.map.length).toEqual(2);
expect(myVivus.map[0].length).toEqual(280);
expect(myVivus.map[1].length).toEqual(141);
});
});
describe('[engine]', function () {
// Mapping
describe('Mapping:', function () {
it('should not trigger any error if the SVG is empty', function () {
expect(function () {
var svgTag = document.createElementNS('http://www.w3.org/2000/svg','svg');
myVivus = new Vivus(svgTag, {});
}).not.toThrow();
});
it('should create a mapping of the SVG', function () {
myVivus = new Vivus(svgTag, {});
expect(myVivus.map && myVivus.map.length).toEqual(6);
});
it('should map with correct values for start and duration', function () {
var i, typeIndex, types = ['delayed', 'sync', 'oneByOne', 'scenario', 'scenario-sync'];
for (typeIndex in types) {
myVivus = new Vivus(svgTag, {type: types[typeIndex], duration: 200});
for (i in myVivus.map) {
expect(myVivus.map[i].startAt >= 0).toBe(true);
expect(myVivus.map[i].duration >= 0).toBe(true);
}
}
});
// Tests for 'getTotalLength' method in case of awkward results
describe('SVG parsing issue', function () {
var getTotalLengthBkp = SVGPathElement.prototype.getTotalLength,
warnBkp = console.warn;
beforeEach(function () {
SVGPathElement.prototype.getTotalLength = function () {
return NaN;
};
});
afterEach(function () {
SVGPathElement.prototype.getTotalLength = getTotalLengthBkp;
console.warn = warnBkp;
});
it('should call console.warn if a path length is NaN', function () {
var warnSpy = jasmine.createSpy('spy');
console.warn = warnSpy;
myVivus = new Vivus(svgTag);
expect(warnSpy.calls.count()).toEqual(6);
expect(myVivus.map.length).toEqual(0);
});
it('shouldn\'t call console.warn if not defined a path length is NaN', function () {
console.warn = null;
myVivus = new Vivus(svgTag);
expect(myVivus.map.length).toEqual(0);
});
});
});
describe('Visibility checking:', function () {
it('should not accept a path which is not displayed', function () {
// Hide a path
svgTag.childNodes[1].style.display = 'none';
myVivus = new Vivus(svgTag, {ignoreInvisible: true});
expect(myVivus.map.length).toEqual(5);
});
it('should not accept a path which with an ignore tag', function () {
svgTag.childNodes[1].setAttribute('data-ignore', 'true');
myVivus = new Vivus(svgTag);
expect(myVivus.map.length).toEqual(5);
});
it('should not accept a path which is not displayed', function () {
svgTag.childNodes[1].setAttribute('data-ignore', 'false');
myVivus = new Vivus(svgTag);
expect(myVivus.map.length).toEqual(6);
});
});
// Drawing
describe('Drawing:', function () {
it('should call the callback once the animation is finished', function () {
var done = false;
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'autostart'
}, function () {
done = true;
});
triggerFrames();
expect(done).toBe(true);
});
it('should call the callback once the reverse animation is finished', function () {
var done = false;
myVivus = new Vivus(svgTag, {
type: 'oneByOne',
duration: 6
}, function () {
done = true;
});
myVivus.finish().play(-1);
triggerFrames();
expect(done).toBe(true);
});
it('should call the method callback as the second param once the animation is finished', function () {
var done = false;
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual',
});
myVivus.play(1, function() {
done = true;
});
triggerFrames();
expect(done).toBe(true);
});
it('should call the method callback as the first param once the animation is finished', function () {
var done = false;
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual',
});
myVivus.play(function() {
done = true;
});
triggerFrames();
expect(done).toBe(true);
});
it('should call the method callback once the reverse animation is finished', function () {
var done = false;
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual',
});
myVivus.finish().play(-1, function() {
done = true;
});
triggerFrames();
expect(done).toBe(true);
});
it('should call the method callback provided in the last play call', function () {
var done = false;
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual',
});
myVivus.finish().play(-1, function () {});
myVivus.play(function() {
done = true;
});
triggerFrames();
expect(done).toBe(true);
});
it('should call destroy method once the animation is finished', function () {
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual',
selfDestroy: true
});
myVivus.destroy = jasmine.createSpy('spy');
myVivus.play();
triggerFrames();
expect(myVivus.destroy.calls.count()).toEqual(1);
});
it('should\' call destroy method if selfDestroy option is not present', function () {
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual'
});
myVivus.destroy = jasmine.createSpy('spy');
myVivus.play();
triggerFrames();
expect(myVivus.destroy.calls.count()).toEqual(0);
});
it('should stop animation if destroy has been called', function () {
var callbackSpy = jasmine.createSpy('spy');
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'autostart'
}, callbackSpy);
triggerFrames(1);
myVivus.destroy();
triggerFrames();
expect(callbackSpy.calls.count()).toEqual(0);
});
it('should stop the animation once it reaches currentFrame == 0', function () {
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual'
});
myVivus.stop = jasmine.createSpy('spy');
myVivus.play(-1);
triggerFrames();
expect(myVivus.stop.calls.count()).toEqual(1);
});
it('should trace reasonably', function () {
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual'
});
spyOn(myVivus, 'trace').and.callThrough();
myVivus.play(0.5);
triggerFrames();
expect(myVivus.trace.calls.count()).toEqual(12);
});
it('should start by the last path if reverseStack is enabled', function () {
myVivus = new Vivus(svgTag, {
type: 'oneByOne',
duration: 5,
reverseStack: true
});
myVivus.setFrameProgress(0.5);
var paths = svgTag.querySelectorAll('path');
expect(+paths[0].style.strokeDashoffset).not.toEqual(0);
expect(+paths[paths.length -1].style.strokeDashoffset).toEqual(0);
});
});
describe('Force Render:', function () {
it('should use renderPath if forceRender option is set to true', function () {
myVivus = new Vivus(svgTag, { duration: 2, start: 'manual', forceRender: true });
var originalFirstPath = myVivus.map[0].el;
myVivus.renderPath(0);
expect(myVivus.map[0].el).not.toBe(originalFirstPath);
});
it('should not use renderPath if forceRender option is set to false', function () {
myVivus = new Vivus(svgTag, { duration: 2, start: 'manual', forceRender: false });
var originalFirstPath = myVivus.map[0].el;
myVivus.renderPath(0);
expect(myVivus.map[0].el).toBe(originalFirstPath);
});
it('renderPath should not throw an error if the index doesn\'t exists', function () {
myVivus = new Vivus(svgTag, { duration: 2, start: 'manual', forceRender: true });
expect(function () {
myVivus.renderPath(42);
}).not.toThrow();
});
});
});
describe('[controls]', function () {
beforeEach(function () {
myVivus = new Vivus(svgTag, {
type: 'oneByOne',
duration: 2,
start: 'manual'
});
});
it('shouldn\'t play if the parameter in incorrect', function () {
expect(function () {myVivus.play('a');}).toThrow(new Error('Vivus [play]: invalid speed'));
expect(function () {myVivus.play({});}).toThrow(new Error('Vivus [play]: invalid speed'));
expect(function () {myVivus.play([]);}).toThrow(new Error('Vivus [play]: invalid speed'));
expect(function () {myVivus.play('1');}).toThrow(new Error('Vivus [play]: invalid speed'));
});
it('should return the correct status', function () {
expect(myVivus.getStatus()).toEqual('start');
myVivus.setFrameProgress(0.5);
expect(myVivus.getStatus()).toEqual('progress');
myVivus.finish();
expect(myVivus.getStatus()).toEqual('end');
myVivus.reset();
expect(myVivus.getStatus()).toEqual('start');
});
it('should play with the normal speed by default', function () {
myVivus.play();
expect(myVivus.speed).toEqual(1);
});
it('shouldn\'t run another process of drawing if the animation is in progress', function () {
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual'
});
spyOn(myVivus, 'trace').and.callThrough();
myVivus.play(0.5);
myVivus.play(0.5);
triggerFrames();
expect(myVivus.trace.calls.count()).toEqual(12);
});
it('should stop the animation only when the animation is running', function () {
myVivus = new Vivus(svgTag, {
duration: 6,
start: 'manual'
});
myVivus.play();
expect(myVivus.handle).toBeTruthy();
myVivus.stop();
expect(myVivus.handle).toBeFalsy();
myVivus.stop();
expect(myVivus.handle).toBeFalsy();
});
it('should remove all unecessary styling on every path element', function () {
var i, paths;
myVivus.destroy();
paths = svgTag.querySelectorAll('path');
for (i = 0; i < paths.length; i++) {
expect(!!paths[i].style.strokeDashoffset).toEqual(false);
expect(!!paths[i].style.strokeDasharray).toEqual(false);
}
});
/**
* Where are the tests about `util` methods?
* Well....
* to be honest, I've been struggling a bit for these kind of tests
* which seems difficult to test from Karma.
*/
});
});