├── .babelrc ├── .eslintrc ├── .gitignore ├── .sass-lint.yml ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE ├── Makefile ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── bower.json ├── package-lock.json ├── package.json ├── src ├── markup │ ├── features │ │ ├── indeterminate.pug │ │ ├── js.pug │ │ ├── mock.pug │ │ ├── positional.pug │ │ ├── sass.pug │ │ ├── simulate.pug │ │ ├── spread.pug │ │ ├── staggered.pug │ │ ├── standard.pug │ │ └── timer.pug │ ├── index.pug │ └── sandbox.pug ├── script │ ├── demo │ │ ├── index.js │ │ ├── sections │ │ │ ├── helper.js │ │ │ ├── mock.js │ │ │ ├── positional.js │ │ │ ├── simulate.js │ │ │ ├── spread.js │ │ │ ├── staggered.js │ │ │ ├── standard.js │ │ │ └── timer.js │ │ ├── styles │ │ │ ├── base.scss │ │ │ └── demo.scss │ │ └── utils.js │ └── index.js └── styles │ ├── _configuration.scss │ ├── _mixins.scss │ ├── core.scss │ ├── ep.scss │ └── features │ ├── helpers.scss │ ├── mock.scss │ ├── simulate.scss │ ├── staggered.scss │ └── timer.scss ├── webpack.config.babel.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "node": true, 5 | "es6": true, 6 | "browser": true 7 | }, 8 | "rules": { 9 | # Possible errors 10 | comma-dangle: 2, 11 | no-console: 2, 12 | no-cond-assign: 2, 13 | no-control-regex: 2, 14 | no-debugger: 2, 15 | no-dupe-args: 2, 16 | no-dupe-keys: 2, 17 | no-duplicate-case: 2, 18 | no-empty: 2, 19 | no-empty-character-class: 2, 20 | no-ex-assign: 2, 21 | no-extra-boolean-cast: 2, 22 | no-extra-semi: 2, 23 | no-func-assign: 2, 24 | no-invalid-regexp: 2, 25 | no-irregular-whitespace: 2, 26 | no-negated-in-lhs: 2, 27 | no-obj-calls: 2, 28 | no-proto: 2, 29 | no-unexpected-multiline: 2, 30 | no-unreachable: 2, 31 | use-isnan: 2, 32 | valid-typeof: 2, 33 | valid-jsdoc: 2, 34 | # Best practices 35 | curly: [2, "multi"], 36 | eqeqeq: 2, 37 | no-fallthrough: 2, 38 | no-param-reassign: 2, 39 | no-octal: 2, 40 | no-redeclare: 2, 41 | # strict mode 42 | strict: [2, "global"], 43 | # variables 44 | no-delete-var: 2, 45 | no-undef: 2, 46 | no-unused-vars: 2, 47 | # node 48 | no-mixed-requires: 2, 49 | no-new-require: 2, 50 | # stylistic 51 | brace-style: 2, 52 | camelcase: 2, 53 | comma-style: 2, 54 | comma-spacing: 2, 55 | eol-last: 2, 56 | indent: [2, 2, {SwitchCase: 1}], 57 | keyword-spacing: 2, 58 | max-len: [2, 80, 2], 59 | max-depth: [2, 4], 60 | new-cap: 2, 61 | new-parens: 2, 62 | no-mixed-spaces-and-tabs: 2, 63 | no-multiple-empty-lines: [2, {max: 2}], 64 | no-trailing-spaces: 2, 65 | quotes: [2, "single"], 66 | semi: 2, 67 | space-before-blocks: [2, "always"], 68 | space-before-function-paren: [2, "never"], 69 | space-in-parens: [2, "never"], 70 | space-infix-ops: 2, 71 | space-unary-ops: 2, 72 | # es6 73 | arrow-parens: [2, "always"], 74 | arrow-spacing: [2, {"before": true, "after": true}], 75 | constructor-super: 2, 76 | no-class-assign: 2, 77 | no-confusing-arrow: 2, 78 | no-const-assign: 2, 79 | no-dupe-class-members: 2, 80 | no-this-before-super: 2, 81 | no-var: 2, 82 | prefer-const: 2, 83 | prefer-rest-params: 2, 84 | template-curly-spacing: 2 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | public/ 3 | dist/ 4 | node_modules/ 5 | test/test.js -------------------------------------------------------------------------------- /.sass-lint.yml: -------------------------------------------------------------------------------- 1 | options: 2 | formatter: stylish 3 | files: 4 | include: 'src/script/entries/ep/**/*.s+(a|c)ss' 5 | rules: 6 | # Extends 7 | extends-before-mixins: 1 8 | extends-before-declarations: 1 9 | placeholder-in-extend: 1 10 | 11 | # Mixins 12 | mixins-before-declarations: 1 13 | 14 | # Line Spacing 15 | one-declaration-per-line: 1 16 | empty-line-between-blocks: 1 17 | single-line-per-selector: 1 18 | 19 | # Disallows 20 | no-attribute-selectors: 0 21 | no-color-hex: 0 22 | no-color-keywords: 1 23 | no-color-literals: 1 24 | no-combinators: 0 25 | no-css-comments: 1 26 | no-debug: 1 27 | no-disallowed-properties: 0 28 | no-duplicate-properties: 1 29 | no-empty-rulesets: 1 30 | no-extends: 0 31 | no-ids: 1 32 | no-important: 1 33 | no-invalid-hex: 1 34 | no-mergeable-selectors: 1 35 | no-misspelled-properties: 1 36 | no-qualifying-elements: 1 37 | no-trailing-whitespace: 1 38 | no-trailing-zero: 1 39 | no-transition-all: 1 40 | no-universal-selectors: 0 41 | no-url-protocols: 1 42 | no-vendor-prefixes: 0 43 | no-warn: 1 44 | property-units: 0 45 | 46 | # Nesting 47 | force-attribute-nesting: 1 48 | force-element-nesting: 1 49 | force-pseudo-nesting: 1 50 | 51 | # Name Formats 52 | class-name-format: 1 53 | function-name-format: 1 54 | id-name-format: 0 55 | mixin-name-format: 1 56 | placeholder-name-format: 1 57 | variable-name-format: 1 58 | 59 | # Style Guide 60 | attribute-quotes: 1 61 | bem-depth: 0 62 | border-zero: 1 63 | brace-style: 1 64 | clean-import-paths: 1 65 | empty-args: 1 66 | hex-length: 1 67 | hex-notation: 1 68 | indentation: 1 69 | leading-zero: 1 70 | nesting-depth: 1 71 | property-sort-order: 1 72 | pseudo-element: 1 73 | quotes: 1 74 | shorthand-values: 1 75 | url-quotes: 1 76 | variable-for-property: 1 77 | zero-unit: 1 78 | 79 | # Inner Spacing 80 | space-after-comma: 1 81 | space-before-colon: 1 82 | space-after-colon: 1 83 | space-before-brace: 1 84 | space-before-bang: 1 85 | space-after-bang: 1 86 | space-between-parens: 1 87 | space-around-operator: 1 88 | 89 | # Final Items 90 | trailing-semicolon: 1 91 | final-newline: 1 92 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Issue summary 2 | 3 | ## Expected behavior 4 | 5 | ## Browser used 6 | 7 | ## Steps to reproduce 8 | 9 | @jh3y 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ep - enhance your progress bars with minimal effort 2 | 3 | https://jh3y.github.io/ep 4 | 5 | MIT License 6 | 7 | Copyright (c) 2016 jh3y 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | MODULES = ./node_modules/.bin 2 | WEBPACK = $(MODULES)/webpack 3 | SERVER = $(MODULES)/webpack-dev-server 4 | MOCHA = $(MODULES)/mocha 5 | BABEL = $(MODULES)/babel 6 | UGLIFY = $(MODULES)/uglifyjs 7 | SASS = $(MODULES)/node-sass 8 | POSTCSS = $(MODULES)/postcss 9 | CLEANCSS = $(MODULES)/cleancss 10 | ESLINT = $(MODULES)/eslint 11 | SASSLINT = $(MODULES)/sass-lint 12 | GHPAGES = $(MODULES)/gh-pages 13 | 14 | DEST = dist 15 | FILE_NAME = ep 16 | SCRIPT_SRC = src/script/index.js 17 | STYLE_SRC = src/styles/ep.scss 18 | 19 | UGLIFY_OPTS = --compress --comments --mangle -o $(DEST)/$(FILE_NAME).min.js $(DEST)/$(FILE_NAME).js 20 | CLEANCSS_OPTS = --s1 -o $(DEST)/$(FILE_NAME).min.css $(DEST)/$(FILE_NAME).css 21 | POSTCSS_OPTS = --use autoprefixer -d $(DEST)/ $(DEST)/*.css 22 | 23 | help: 24 | @grep -E '^[a-zA-Z\._-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 25 | 26 | bundle: ## bundles source 27 | $(WEBPACK) --progress --colors 28 | 29 | deploy: ## create deployment bundle 30 | rm -rf public && $(WEBPACK) --progress --colors -p --deploy && $(GHPAGES) -d public 31 | 32 | # NOTE:: Introduce when tests are created 33 | # test-bundle: ## bundles test source 34 | # $(WEBPACK) --config webpack.config.test.babel.js --progress --colors 35 | 36 | # test: test-bundle ## runs tests 37 | # $(MOCHA) test/test.js 38 | 39 | test: ## runs tests, linting 40 | make lint 41 | 42 | develop: ## develop source 43 | $(SERVER) --progress --colors -d --hot --inline 44 | 45 | setup: ## sets up project 46 | npm install 47 | 48 | dist-style: ## compiles styles for dist 49 | mkdir -pv $(DEST) && $(SASS) $(STYLE_SRC) $(DEST)/$(FILE_NAME).css && $(POSTCSS) $(POSTCSS_OPTS) && $(CLEANCSS) $(CLEANCSS_OPTS) 50 | 51 | dist-script: ## compiles script for dist 52 | mkdir -pv $(DEST) && $(BABEL) $(SCRIPT_SRC) -o $(DEST)/$(FILE_NAME).js && $(UGLIFY) $(UGLIFY_OPTS) 53 | 54 | clean: ## removes directories 55 | rm -rf $(DEST) public 56 | 57 | dist: clean ## create dist scripts 58 | rm -rf $(DEST) && mkdir -pv $(DEST) && make dist-script && make dist-style 59 | 60 | lint-scripts: ## lints ep script 61 | $(ESLINT) $(SCRIPT_SRC) 62 | 63 | lint-styles: ## lints ep stylesheet 64 | $(SASSLINT) --verbose 65 | 66 | lint: ## lints source 67 | make lint-styles && make lint-scripts 68 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Fixes # || Adds new feature X 2 | 3 | Changes include; 4 | * a 5 | * b 6 | * c 7 | 8 | Checks 9 | - [ ] Passes linting 10 | - [ ] Updated documentation (_if necessary_) 11 | - [ ] Updated versioning across __both__ source and package files 12 | 13 | @jh3y 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/jh3y/ep.svg?branch=master)](https://travis-ci.org/jh3y/ep) 2 | 3 | ![alt tag](https://github.com/jh3y/pics/blob/master/ep/ep.png) 4 | ## enhance your HTML5 `` bars with minimal effort! 5 | 6 | ![alt tag](https://github.com/jh3y/pics/blob/master/ep/ep.gif) 7 | 8 | The `` element doesn't always play nice. 9 | 10 | It doesn't have a consistent appearance across the popular browsers. In addition, different browsers impose different limitations on the `` element. 11 | 12 | Because of this, it's often overlooked in favor of styled `
` combos. 13 | 14 | `ep` tackles this; 15 | 16 | * Cross browser reset and styling to pull `` element in line with modern slim-style bars 17 | * CSS helpers that provide classes and attributes to deal with things like positioning, growth style, simulation etc. 18 | * Optional JS helper for better control and interaction with `` elements. For example; being able to hook into network request status and display this to the end user. 19 | * Plays nice wherever the `` element is supported! 20 | 21 | ```js 22 | const myEp = new Ep(document.querySelector('progress')); 23 | const makeRequest = () => { 24 | myEp.simulate(); 25 | const xhttp = new XMLHttpRequest(); 26 | xhttp.onreadystatechange = function() { 27 | if (this.readyState == 4 && this.status == 200) { 28 | myEp.complete(); 29 | } 30 | }; 31 | xhttp.open('GET', '/index.html', true); 32 | xhttp.send(); 33 | }; 34 | (myEp._VALUE) ? myEp.set(0, makeRequest) : makeRequest(); 35 | ``` 36 | 37 | ## Index 38 | 39 | * [Browser support](https://github.com/jh3y/ep#browser-support) 40 | * [Caveats](https://github.com/jh3y/ep#caveats) 41 | * [Usage](https://github.com/jh3y/ep#usage) 42 | * [Install](https://github.com/jh3y/ep#install) 43 | * [Just using the stylesheet](https://github.com/jh3y/ep#just-using-the-stylesheet) 44 | * [Including the optional JS helper](https://github.com/jh3y/ep#including-the-optional-js-helper) 45 | * [Integrating with your own SASS files](https://github.com/jh3y/ep#integrating-with-your-own-sass-files) 46 | * [CSS Helpers Api](https://github.com/jh3y/ep#css-helpers-api) 47 | * [Aesthetic helpers](https://github.com/jh3y/ep#aesthetic-helpers) 48 | * [Behavioural helpers](https://github.com/jh3y/ep#behavioural-helpers) 49 | * [Sass variables](https://github.com/jh3y/ep#sass-variables) 50 | * [Sass mixin](https://github.com/jh3y/ep#sass-mixin) 51 | * [Javascript Helper Api](https://github.com/jh3y/ep#javascript-helper-api) 52 | * [Hooking into network requests](https://github.com/jh3y/ep#hooking-into-network-requests) 53 | * [What happened to progrecss?](https://github.com/jh3y/ep#what-happened-to-progrecss) 54 | * [Development](https://github.com/jh3y/ep#development) 55 | * [Contributing](https://github.com/jh3y/ep#contributing) 56 | * [License](https://github.com/jh3y/ep#license) 57 | 58 | 59 | ## Browser support 60 | 61 | | Chrome | Firefox | Safari | Opera | Edge | IE(_10+_)| 62 | | ------------- |-------------| -----| -----|-----|-----| 63 | | :smile: | :smile: | :smile: | :smile: | :smile: | :smile: | 64 | 65 | ### Caveats 66 | * iOS Safari doesn't like indeterminate `` elements. Get round this by not setting your `` element to be indeterminate but instead using the helper class `ep--indeterminate` which will mock the indeterminate state. 67 | * In IE, ensure that the `max` attribute is set when using specific values. If no `max` is set, the value won't go higher than `1` :cry: 68 | 69 | ## Usage 70 | You have various options with how to use `ep`; 71 | 72 | * Use the stylesheet by adding [`ep.css`](https://github.com/jh3y/ep/blob/master/dist/ep.css) to your app. 73 | * Include the optional JS helper [`ep.js`](https://github.com/jh3y/ep/blob/master/ep.js) for some more control. 74 | * Integrate the `ep` styles with your own SASS by importing the parts you need. 75 | 76 | ### Install 77 | You can grab a copy of `ep` through `bower` or `npm`; 78 | 79 | ```shell 80 | bower install ep 81 | 82 | npm install @jh3y/ep 83 | ``` 84 | 85 | ### Just using the stylesheet 86 | If you're just using the stylesheet, you just need to include it. No alterations need to be made to your current `` elements unless you want to make use of some of the helper classes and attributes. 87 | 88 | ```html 89 | 90 | 91 | 92 | 93 | 94 | ``` 95 | 96 | Need to change the color of a `` element or something else? Override the rule. For example, change the color to `purple`; 97 | ```css 98 | progress { 99 | background: purple; 100 | } 101 | 102 | progress::-moz-progress-bar { 103 | background: purple; 104 | } 105 | 106 | progress::-webkit-progress-bar { 107 | background: purple; 108 | } 109 | 110 | progress::-webkit-progress-value { 111 | background: purple; 112 | } 113 | ``` 114 | There is also a `SASS` mixin included to as a shorthand to do this; 115 | ```sass 116 | @import '~ep/mixins'; 117 | .progress { 118 | @include color-bar(purple); 119 | } 120 | ``` 121 | ### Including the optional JS helper 122 | If you choose to use the optional JS helper. You'll have access to the `Ep` constructor class. Refer to the JS API for further info. 123 | 124 | ```js 125 | const el = document.querySleect 126 | const myEp = new Ep() 127 | ``` 128 | 129 | ### Integrating with your own SASS files 130 | Say you want to pick and choose pieces of `ep`. Simple. This is actually the easiest way to override `ep`s configuration variables. `ep` makes use of the `!default` flag in `sass` to make this possible. For example; let's say we only want the core styles but we don't want any opacity and we want the primary color to be be purple. 131 | 132 | ```sass 133 | $ep-fg: purple; 134 | $ep-opacity: 1; 135 | @import '~ep/core' 136 | ``` 137 | 138 | ### CSS Helpers API 139 | Without the use of JS, `ep` provides some CSS helpers in the form of attributes and classes you can apply to `` elements to define behaviors. 140 | 141 | #### Aesthetic helpers 142 | Aesthetic helpers come in the form of classes; 143 | 144 | * `ep--top` - position absolute top 145 | * `ep--bottom` - position absolute bottom 146 | * `ep--fixed` - position fixed 147 | * `ep--spread` - change style of `` bar to spread 148 | * `ep--indeterminate` - show indeterminate state 149 | 150 | #### Behavioural helpers 151 | Behavioural helpers come in the form of attributes that must be applied to your `` elements; 152 | 153 | * `data-complete` - complete progress(_set to 100 and hide, more control with JS helper_) 154 | * `data-simulate` - slowly simulate progress in steps up to `99%`over 30 seconds(_request timeout_), can be configured/overrode 155 | * `data-mock="value"` - mock progress for given number of seconds 156 | * `data-staggered-mock="value"` - mock progress but with staggered style 157 | * `data-timer="value"` - use progress element as timer for given seconds 158 | * `data-pause` - pauses any animation in place such as timer, mock etc. 159 | 160 | _NOTE::_ The `mock`, `staggered-mock`, `timer` and `simulate` behaviors have duration defaults set in the source. For example; the max duration is set at `4`. This is to keep the size down. But these can easily be altered by building your own version of `ep` or adding rules for the durations you require. For example; I want the simulation to only take `10s` and a timer that will take `15s`. 161 | 162 | ```css 163 | progress[data-simulate] { 164 | animation-duration: 10s; 165 | animation-timing-function: steps(28); 166 | } 167 | 168 | progress[data-timer="15"] { 169 | animation-duration: 15s; 170 | } 171 | 172 | ``` 173 | 174 | #### Sass variables 175 | `ep` leverages the `!default` flag in Sass so it's easier to override `ep` configuration variables. Simply set any of the following variables before importing `ep`. 176 | 177 | * `$ep-ns: ep` - set the class helper prefix 178 | * `$ep-height: 6px` - set the height of `` elements 179 | * `$ep-fg: #3498db` - set the primary color of `` elements 180 | * `$ep-indeterminate-fg: $ep-fg` - set the primary color when in indeterminate state 181 | * `$ep-opacity: .6` - set the opacity of `` elements 182 | * `$ep-transition: .25s` - set the duration for `` elements to transition value 183 | * `$ep-timeout-threshold: 30` - the time it takes for simulation to complete in seconds 184 | * `$ep-simulate-max: 99` - at which value should simulation stop 185 | * `$ep-mocks-min: 1` - minimum mocking duration in seconds 186 | * `$ep-mocks-max: 4` - maximum mocking duration in seconds 187 | * `$ep-staggered-mocks-min: 1` - minimum staggered mock duration in seconds 188 | * `$ep-staggered-mocks-max: 4` - maximum staggered mock duration in seconds 189 | * `$ep-timers-min: 1` - minimum timer duration in seconds 190 | * `$ep-timers-max: 4` - maximum timer duration in seconds 191 | 192 | ```sass 193 | $ep-fg: #e74c3c3; 194 | $ep-opacity: .8; 195 | // import with default variables override 196 | @import '~ep/core'; 197 | ``` 198 | 199 | #### Sass mixin 200 | `ep` also has a mixin available for coloring `progress` elements. Simply pass a color to `color-bar`. You may use this without even importing the `ep` reset if you just want to color some `progress` elements. 201 | 202 | ```sass 203 | @import '~ep/mixins'; 204 | 205 | .my-progress-element { @include color-bar(#7bff7b); } 206 | 207 | ``` 208 | 209 | ### Javascript helper/API 210 | `ep` also provides an optional Javascript helper/api. This can be used for convenience and also gives a little more control and power when interacting with `` elements. It doesn't create any extra elements, but you must pass a `HTMLProgressElement` into the constructor. 211 | 212 | ```js 213 | const bar = document.querySelector('progress'); 214 | const myEp = new Ep(bar); 215 | ``` 216 | It's main purpose is that it saves you the burden of having to set/remove attributes/classes. But it also provides some nice to haves such as being able to hook into when progress is complete or set. 217 | 218 | The source is quite heavily documented and written with `babel` so be sure to check that out [here](https://github.com/jh3y/ep/blob/master/src/script/entries/ep/index.js). 219 | 220 | As for the methods available(`?` denotes an optional parameter); 221 | 222 | * `set({number} val, ? {function} cb)` - Sets `` value with optional callback. 223 | * `setSpread(? {bool} spread)` - Set whether `` element should be spred style. By default will set to false. 224 | * `setIndeterminate(? {bool} indeterminate)` - Set whether `` element is using `indeterminate` helper class. By default, will remove helper class. 225 | * `togglePause` - Toggles pause attribute for play/pause animation. 226 | * `setPosition(? {Array string} positions)` - Takes an optional array of positions that will be applied to the element. For example, `['top', 'fixed']` will set `ep--top ep--fixed` class to the `` element. If no positions are declared, all currently applied will be wiped. 227 | * `increase(? {number} value, ? {function} cb)` - Increase progress value by optional increment with an optional callback. By default, increment is 5. 228 | * `decrease(? {number} value, ? {function} cb)` - Decrease progress value by optional decrement with an optional callback. By default, decrement is 5. 229 | * `reset` - Resets `` value to 0. 230 | * `mock(? {number} duration, ? {bool} staggered, ? {function} cb)` - Mocks progress with a mocking animation. Optional duration in seconds. Optional staggered attribute defines which mock style use. Optional callback can be invoked when mock is complete. By default, duration is 4. 231 | * `time(? {number} duration, ? {function} cb)` - Timing mock for using element as timer. Optional duration in seconds. Optional callback can be invoked when timer is complete. By default, duration is 4. 232 | * `simulate(? {number} step, ? {number} increment, ? {number} max)` - Simulation on the Javascript side is an example where we have more control than we do with CSS. Set a simulation by declaring a step duration in `ms`, an `increment` and a `max` value for the simulation to reach. The default simulation will increment by 5 every second until the `` element has a value of 99. 233 | * `complete(? {function} cb)` - Complete a progress bar by setting value to 100 and then resetting it. Provide optional callback for when complete. 234 | 235 | #### Hooking into network requests 236 | Yep. You can easily integrate `ep` to communicate network request status to the end user. The most basic example being something like the following; 237 | ```js 238 | const myEp = new Ep(document.querySelector('progress')); 239 | const makeRequest = () => { 240 | myEp.simulate(); 241 | const xhttp = new XMLHttpRequest(); 242 | xhttp.onreadystatechange = function() { 243 | if (this.readyState == 4 && this.status == 200) { 244 | myEp.complete(); 245 | } 246 | }; 247 | xhttp.open('GET', '/index.html', true); 248 | xhttp.send(); 249 | } 250 | (myEp._VALUE) ? myEp.set(0, makeRequest) : makeRequest(); 251 | ``` 252 | We start with a simple `progress` element. Reset it to make sure it starts at `0` and start a simulation. When we get the `OK` from our network request, set our element to complete :tada: 253 | 254 | ## What happened to progrecss? 255 | For some time, I'd intended to revisit `progre(c)ss` with some ideas I had. When I finally got round to it, I went back over the issues and something struck me. Someone had pointed out why not use the `` element? 256 | 257 | I'd previously struck this off because I liked being able to add `:pseudo` element progress bars to any element with relative ease where the `:pseudo` elements were available. 258 | 259 | However, using `:pseudo` elements to display progress isn't ideal and not very semantic. 260 | 261 | It makes more sense to create something that can be integrated without big changes. 262 | 263 | `progre(c)ss` is still available under the release tab if you really want it but realistically the code for `progre(c)ss` is as simple as; 264 | 265 | ```sass 266 | .progrecss { 267 | &:before { 268 | color: green; 269 | content: ''; 270 | height: 6px; 271 | left: 0; 272 | opacity: .8; 273 | position: absolute; 274 | top: 0; 275 | } 276 | @for $percent from 1 through 100 { 277 | &[data-progrecss-value='#{$percent}'] { 278 | &:before { 279 | width: $percent * 1%; 280 | } 281 | } 282 | } 283 | } 284 | ``` 285 | 286 | ## Development 287 | `ep` is developed using `webpack`, `webpack-dev-server`, `babel` and `sass`. 288 | 289 | It uses a self-documented `Makefile` for development. 290 | 291 | ### See available tasks 292 | ```shell 293 | make 294 | ``` 295 | 296 | ### Setup 297 | ```shell 298 | make setup 299 | ``` 300 | 301 | ### Start developing 302 | ```shell 303 | make develop 304 | ``` 305 | 306 | 307 | ## Contributing 308 | Don't hesitate to post and issue, PR or suggestion. Alternatively, get in touch via email or by tweeting me [@_jh3y](https://twitter.com/_jh3y)! :smile: 309 | 310 | ## License 311 | MIT 312 | 313 | ----------------- 314 | 315 | Made real by [@jh3y](https://twitter.com/_jh3y) 2017 :sunglasses: 316 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ep", 3 | "main": "dist/ep.css", 4 | "version": "2.0.4", 5 | "homepage": "http://jh3y.github.io/ep", 6 | "authors": [ 7 | "jh3y " 8 | ], 9 | "description": "enhance your progress bars with minimal effort!", 10 | "keywords": [ 11 | "ep", 12 | "progress", 13 | "css", 14 | "css3", 15 | "es6" 16 | ], 17 | "license": "MIT", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "test" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jh3y/ep", 3 | "version": "2.0.6", 4 | "description": "enhance your progress bars with minimal effort!", 5 | "main": "dist/ep.js", 6 | "scripts": { 7 | "prepublish": "make dist", 8 | "test": "make test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/jh3y/ep.git" 13 | }, 14 | "keywords": [ 15 | "progress", 16 | "ep", 17 | "css", 18 | "ui", 19 | "ux", 20 | "loader" 21 | ], 22 | "author": "jh3y ", 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/jh3y/ep/issues" 26 | }, 27 | "homepage": "https://jh3y.github.io/ep", 28 | "devDependencies": { 29 | "autoprefixer": "^6.5.0", 30 | "babel-cli": "^6.16.0", 31 | "babel-core": "^6.14.0", 32 | "babel-eslint": "^7.0.0", 33 | "babel-loader": "^6.2.5", 34 | "babel-preset-es2015": "^6.14.0", 35 | "babel-register": "^6.14.0", 36 | "clean-css": "^3.4.20", 37 | "css-loader": "^0.25.0", 38 | "eslint": "^3.6.1", 39 | "extract-text-webpack-plugin": "^1.0.1", 40 | "gh-pages": "^0.11.0", 41 | "html-loader": "^0.4.4", 42 | "html-webpack-plugin": "^2.22.0", 43 | "node-sass": "^3.10.0", 44 | "normalize.css": "^4.2.0", 45 | "postcss-cli": "^2.6.0", 46 | "postcss-loader": "^0.11.1", 47 | "prismjs": "^1.5.1", 48 | "pug": "^2.0.0-beta6", 49 | "pug-loader": "^2.3.0", 50 | "sass-lint": "^1.9.1", 51 | "sass-loader": "^4.0.2", 52 | "style-loader": "^0.13.1", 53 | "uglify-js": "^2.7.3", 54 | "webpack": "^1.13.2", 55 | "webpack-dev-server": "^1.15.1" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/markup/features/indeterminate.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--indeterminate 2 | h2 Indeterminate 3 | .wrapper 4 | pre.demo__markup 5 | code.language-html. 6 | <progress></progress> 7 | 8 | p.demo__doc. 9 | If a progress bar is indeterminate, show a standardized animation. This animation has to be applied to the progress element itself and not its pseudo elements for cross browser compatibility. 10 | p.demo__doc. 11 | Indeterminate progress elements don't play nice in iOS Safari so use the helper class ep--indeterminate. 12 | -------------------------------------------------------------------------------- /src/markup/features/js.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--js 2 | h2 JS helper 3 | pre.demo__markup 4 | code.language-js. 5 | const el = document.getElementById('js-bar'); 6 | const myEp = new ep(el); 7 | p.demo__doc. 8 | For better control over progress behavior, ep comes with a JS helper. This allows you to do various things such as hook into value changes etc. 9 | progress.demo-bar(id="js-bar", value="75", max="100") 10 | p.demo__doc. 11 | Have a play with some of the following examples; 12 | .demo-control-group 13 | pre.demo__markup 14 | code.language-js. 15 | myEp.simulate(); 16 | const xhttp = new XMLHttpRequest(); 17 | xhttp.onreadystatechange = function() { 18 | if (this.readyState == 4 && this.status == 200) { 19 | myEp.complete(); 20 | } 21 | }; 22 | xhttp.open('GET', '/index.html', true); 23 | xhttp.send(); 24 | button(class="ajax") Run 25 | .demo-control-group 26 | pre.demo__markup 27 | code.language-js. 28 | const afterSet = (e) => { 29 | alert('All set!'); 30 | }; 31 | myEp.set(23, afterSet); 32 | button(class="afterSet") Run 33 | .demo-control-group 34 | pre.demo__markup 35 | code.language-js. 36 | const onComplete = (e) => { 37 | alert('Completed!'); 38 | }; 39 | myEp.complete(onComplete); 40 | button(class="complete") Run 41 | .demo-control-group 42 | pre.demo__markup 43 | code.language-js. 44 | myEp.setPosition(['top', 'fixed']); 45 | button(class="position") Run 46 | .demo-control-group 47 | pre.demo__markup 48 | code.language-js. 49 | myEp.simulate(); 50 | button(class="simulate") Run 51 | .demo-control-group 52 | pre.demo__markup 53 | code.language-js. 54 | myEp.set(75); 55 | button(class="set") Run 56 | .demo-control-group 57 | pre.demo__markup 58 | code.language-js. 59 | myEp.reset(); 60 | button(class="reset") Run 61 | .demo-control-group 62 | pre.demo__markup 63 | code.language-js. 64 | myEp.increase(); 65 | button(class="increase") Run 66 | .demo-control-group 67 | pre.demo__markup 68 | code.language-js. 69 | myEp.decrease(25); 70 | button(class="decrease") Run 71 | .demo-control-group 72 | pre.demo__markup 73 | code.language-js. 74 | myEp.setPosition(); 75 | button(class="resetPosition") Run 76 | .demo-control-group 77 | pre.demo__markup 78 | code.language-js. 79 | myEp.setSpread(true); 80 | button(class="spreadTrue") Run 81 | .demo-control-group 82 | pre.demo__markup 83 | code.language-js. 84 | myEp.setSpread(false); 85 | button(class="spreadFalse") Run 86 | -------------------------------------------------------------------------------- /src/markup/features/mock.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--mock 2 | h2 Mocked progress 3 | .wrap 4 | pre.demo__markup(id="mock-markup") 5 | code.language-html. 6 | <progress data-mock="4"> 7 | </progress> 8 | progress(id='mock-bar', data-mock='4') 9 | p.demo__doc. 10 | Maybe you'd rather just mock progress. Just set a duration(seconds) with the data-mock attribute. Using the ep JS helper, we can hook into when our mock is complete using a callback. Check the API in the docs! 11 | fieldset.demo__controls 12 | legend Set mock speed 13 | .demo-control-group 14 | label Value 15 | input(id='mock-set', data-for='mock-bar', type='number', max='4', min='1', value='4') 16 | -------------------------------------------------------------------------------- /src/markup/features/positional.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--positional 2 | h2 Positional CSS helpers 3 | pre.demo__markup(id="positional-markup") 4 | code.language-html. 5 | <progress class="ep--top"></progress> 6 | progress.ep--top(id="positional-bar", value="75", max="100") 7 | p.demo__doc. 8 | ep provides some quick use CSS helpers for positioning your progress bars. For example, you may decide to have some of your progress bars fixed top as commonly seen in some web apps. 9 | fieldset.demo__controls 10 | legend Set position 11 | .demo-control-group 12 | label None 13 | input(type="radio", name="position", value="") 14 | .demo-control-group 15 | label Top 16 | input(type="radio", name="position", value="ep--top", checked) 17 | .demo-control-group 18 | label Fixed top 19 | input(type="radio", name="position", value="ep--top ep--fixed") 20 | .demo-control-group 21 | label Bottom 22 | input(type="radio", name="position", value="ep--bottom") 23 | .demo-control-group 24 | label Fixed bottom 25 | input(type="radio", name="position", value="ep--bottom ep--fixed") 26 | -------------------------------------------------------------------------------- /src/markup/features/sass.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--sass 2 | h2 SASS integration 3 | p.demo__doc. 4 | ep is written with sass using !default variables for configuration. 5 | p.demo__doc. 6 | This allows you to import just the pieces of ep you want whilst being able to override the defaults from within your own sass code. 7 | p.demo__doc. 8 | For example; how about overriding the color and opacity? 9 | pre.demo__markup 10 | code.language-css. 11 | $ep-fg: #e74c3c; 12 | $ep-opacity: .8; 13 | // import with variable configuration overridden 14 | @import '~ep/core'; 15 | -------------------------------------------------------------------------------- /src/markup/features/simulate.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--simulate 2 | h2 Simulate progress 3 | .wrap 4 | pre.demo__markup(id="simulate-markup") 5 | code.language-html. 6 | <progress data-simulate="true"> 7 | </progress> 8 | code.language-html.markup--complete. 9 | <progress data-complete="true"> 10 | </progress> 11 | progress(id='simulate-bar', data-simulate='true') 12 | p.demo__doc. 13 | How about ways to simulate progress whilst background processes are taking place? We can set a simulation with a given time and animation steps. We can make it so that it never reaches 100% until we are ready. When we are ready, set the progress element to complete. 14 | p.demo__doc. 15 | More power over this behavior can be had by using the JavaScript helpers simulate method. 16 | button(id='complete-simulate', data-for='simulate-bar') Complete progress 17 | -------------------------------------------------------------------------------- /src/markup/features/spread.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--spread 2 | h2 Spread CSS helper 3 | .wrap 4 | pre.demo__markup(id="spread-markup") 5 | code.language-html. 6 | <progress class="ep--spread" value='75'></progress> 7 | code.language-html.markup--complete. 8 | <progress class="ep--spread" data-complete="true"> 9 | </progress> 10 | progress.ep--spread(id="spread-bar", value="75", max="100") 11 | p.demo__doc. 12 | ep provides a CSS helper for changing progress bar style. Instead of from left to right and vice-versa, you can spread a progress bar out from the middle instead. 13 | fieldset.demo__controls 14 | legend Set progress 15 | .demo-control-group 16 | label() Value 17 | input(id="spread-set", type="number", max="100", min="0", value="75") 18 | .demo-control-group 19 | button(id="spread-complete", data-for="spread-bar") Complete progress 20 | -------------------------------------------------------------------------------- /src/markup/features/staggered.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--staggered 2 | h2 Staggered mock progress 3 | .wrap 4 | pre.demo__markup(id="staggered-markup") 5 | code.language-html. 6 | <progress data-staggered-mock="4"> 7 | </progress> 8 | progress(id='staggered-bar', data-staggered-mock='4') 9 | p.demo__doc. 10 | Add a little stagger to your mocks. 11 | fieldset.demo__controls 12 | legend Set mock speed 13 | .demo-control-group 14 | label Value 15 | input(id='staggered-set', data-for='staggered-bar', type='number', max='4', min='1', value='4') 16 | -------------------------------------------------------------------------------- /src/markup/features/standard.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--standard 2 | h2 Standard usage 3 | .wrap 4 | pre.demo__markup(id="standard-markup") 5 | code.language-html. 6 | <progress value='75'></progress> 7 | code.language-html.markup--complete. 8 | <progress data-complete="true"> 9 | </progress> 10 | progress.demo-bar(id="standard-bar", value="75", max="100") 11 | p.demo__doc. 12 | Standard usage is no different to that when using the progress element as you would normally. 13 | p.demo__doc. 14 | However, if you want to complete progress at any stage, you swap out the value attribute for a data-complete attribute. 15 | fieldset.demo__controls 16 | legend Set progress 17 | .demo-control-group 18 | label() Value 19 | input(id="standard-set", type="number", max="100", min="0", value="75") 20 | .demo-control-group 21 | button(id="standard-complete", data-for="standard-bar") Complete progress 22 | -------------------------------------------------------------------------------- /src/markup/features/timer.pug: -------------------------------------------------------------------------------- 1 | section.demo.demo--timer 2 | h2 Timer 3 | .wrap 4 | pre.demo__markup(id="timer-markup") 5 | code.language-html. 6 | <progress data-timer="4"> 7 | </progress> 8 | code.language-html.markup--complete. 9 | <progress data-timer="4" data-pause="true"> 10 | </progress> 11 | progress(id='timer-bar', data-timer='4') 12 | p.demo__doc. 13 | Flip progress usage and use it as a timer instead. If you need to pause it any stage, use a pause attribute. 14 | fieldset.demo__controls 15 | legend Set timer speed 16 | .demo-control-group 17 | label Value 18 | input(id='timer-set', data-for='timer-bar', type='number', max='4', min='1', value='4') 19 | .demo-control-group 20 | button(id="pause-timer") Toggle pause 21 | -------------------------------------------------------------------------------- /src/markup/index.pug: -------------------------------------------------------------------------------- 1 | mixin githubStats(userName, repoName) 2 | .stats 3 | .stats__stars 4 | iframe(src = `http://ghbtns.com/github-btn.html?user=${userName}&repo=${repoName}&type=watch&count=true`, allowtransparency="true", frameborder="0", scrolling="0", width="90px", height="20") 5 | .stats__forks 6 | iframe(src = `http://ghbtns.com/github-btn.html?user=${userName}&repo=${repoName}&type=fork&count=true`, allowtransparency="true", frameborder="0", scrolling="0", width="90px", height="20") 7 | .stats__follow 8 | iframe(src = `http://ghbtns.com/github-btn.html?user=${userName}&repo=${repoName}&type=follow&count=true`, allowtransparency="true", frameborder="0", scrolling="0", width="120px", height="20") 9 | html(lang="en-us") 10 | head 11 | meta(charset="utf-8") 12 | meta(http-equiv="X-UA-Compatible", content="IE-edge") 13 | title ep | enhance your progress bars with minimal effort! 14 | meta(name="viewport", content="width=device-width, initial-scale=1, user-scalable=0, maximum-scale=1.0") 15 | meta(name="description", content="html5 progress enhancement with minimal effort!") 16 | body 17 | progress#top-bar.ep--top.ep--fixed(value="0", max="100", data-mock="2") 18 | .content 19 | header.header 20 | .header__title 21 | img(src="data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiIHN0YW5kYWxvbmU9Im5vIj8+CjxzdmcKICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICB4bWxuczpjYz0iaHR0cDovL2NyZWF0aXZlY29tbW9ucy5vcmcvbnMjIgogICB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiCiAgIHhtbG5zOnN2Zz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciCiAgIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiCiAgIHZlcnNpb249IjEuMSIKICAgaWQ9InN2ZzIiCiAgIHZpZXdCb3g9IjAgMCA2MC4wMDAwMDEgNjAuMDAwMDAxIgogICBoZWlnaHQ9IjYwIgogICB3aWR0aD0iNjAiPgogIDxkZWZzCiAgICAgaWQ9ImRlZnM0Ij4KICAgIDxsaW5lYXJHcmFkaWVudAogICAgICAgaWQ9ImxpbmVhckdyYWRpZW50NDIwMCI+CiAgICAgIDxzdG9wCiAgICAgICAgIGlkPSJzdG9wNDIwMiIKICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICBzdHlsZT0ic3RvcC1jb2xvcjojN2I3YmZmO3N0b3Atb3BhY2l0eToxOyIgLz4KICAgICAgPHN0b3AKICAgICAgICAgaWQ9InN0b3A0MjA0IgogICAgICAgICBvZmZzZXQ9IjEiCiAgICAgICAgIHN0eWxlPSJzdG9wLWNvbG9yOiM3YjdiZmY7c3RvcC1vcGFjaXR5OjA7IiAvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudAogICAgICAgaWQ9ImxpbmVhckdyYWRpZW50NDE1NyI+CiAgICAgIDxzdG9wCiAgICAgICAgIGlkPSJzdG9wNDE1OSIKICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICBzdHlsZT0ic3RvcC1jb2xvcjojN2I3YmZmO3N0b3Atb3BhY2l0eToxOyIgLz4KICAgICAgPHN0b3AKICAgICAgICAgaWQ9InN0b3A0MTYxIgogICAgICAgICBvZmZzZXQ9IjEiCiAgICAgICAgIHN0eWxlPSJzdG9wLWNvbG9yOiM3YjdiZmY7c3RvcC1vcGFjaXR5OjA7IiAvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxsaW5lYXJHcmFkaWVudAogICAgICAgaWQ9ImxpbmVhckdyYWRpZW50NDE0MCI+CiAgICAgIDxzdG9wCiAgICAgICAgIGlkPSJzdG9wNDE0MiIKICAgICAgICAgb2Zmc2V0PSIwIgogICAgICAgICBzdHlsZT0ic3RvcC1jb2xvcjojN2I3YmZmO3N0b3Atb3BhY2l0eToxOyIgLz4KICAgICAgPHN0b3AKICAgICAgICAgaWQ9InN0b3A0MTQ0IgogICAgICAgICBvZmZzZXQ9IjEiCiAgICAgICAgIHN0eWxlPSJzdG9wLWNvbG9yOiM3YjdiZmY7c3RvcC1vcGFjaXR5OjAuMDAzOTIxNTciIC8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50CiAgICAgICBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIKICAgICAgIHkyPSIxMDIyLjAyNzIiCiAgICAgICB4Mj0iLTEyLjMwOTQyIgogICAgICAgeTE9IjEwMjIuMDI3MiIKICAgICAgIHgxPSItNjcuMzA5NDIiCiAgICAgICBpZD0ibGluZWFyR3JhZGllbnQ0MjA2IgogICAgICAgeGxpbms6aHJlZj0iI2xpbmVhckdyYWRpZW50NDIwMCIgLz4KICA8L2RlZnM+CiAgPG1ldGFkYXRhCiAgICAgaWQ9Im1ldGFkYXRhNyI+CiAgICA8cmRmOlJERj4KICAgICAgPGNjOldvcmsKICAgICAgICAgcmRmOmFib3V0PSIiPgogICAgICAgIDxkYzpmb3JtYXQ+aW1hZ2Uvc3ZnK3htbDwvZGM6Zm9ybWF0PgogICAgICAgIDxkYzp0eXBlCiAgICAgICAgICAgcmRmOnJlc291cmNlPSJodHRwOi8vcHVybC5vcmcvZGMvZGNtaXR5cGUvU3RpbGxJbWFnZSIgLz4KICAgICAgICA8ZGM6dGl0bGU+PC9kYzp0aXRsZT4KICAgICAgPC9jYzpXb3JrPgogICAgPC9yZGY6UkRGPgogIDwvbWV0YWRhdGE+CiAgPGcKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLC05OTIuMzYyMTYpIgogICAgIGlkPSJsYXllcjEiPgogICAgPHJlY3QKICAgICAgIHRyYW5zZm9ybT0ibWF0cml4KDAuOTk3NjcwNywtMC4wNjgyMTQxMywwLjA2ODIxNDEzLDAuOTk3NjcwNywwLDApIgogICAgICAgeT0iOTk0LjgxNDAzIgogICAgICAgeD0iLTY3LjAyMjU2OCIKICAgICAgIGhlaWdodD0iNTQuNDI2Mjk2IgogICAgICAgd2lkdGg9IjU0LjQyNjI5NiIKICAgICAgIGlkPSJyZWN0NDE5NCIKICAgICAgIHN0eWxlPSJmaWxsOnVybCgjbGluZWFyR3JhZGllbnQ0MjA2KTtmaWxsLW9wYWNpdHk6MTtzdHJva2U6IzAwMDAwMDtzdHJva2Utd2lkdGg6MC41NzM3MDM1MztzdHJva2UtbGluZWNhcDpzcXVhcmU7c3Ryb2tlLWxpbmVqb2luOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjQ7c3Ryb2tlLWRhc2hhcnJheTpub25lO3N0cm9rZS1vcGFjaXR5OjEiIC8+CiAgICA8dGV4dAogICAgICAgaWQ9InRleHQ0MTk2IgogICAgICAgeT0iMTAyOS4xMTAxIgogICAgICAgeD0iNy45MjI3MDA5IgogICAgICAgc3R5bGU9ImZvbnQtc3R5bGU6aXRhbGljO2ZvbnQtdmFyaWFudDpub3JtYWw7Zm9udC13ZWlnaHQ6bm9ybWFsO2ZvbnQtc3RyZXRjaDpub3JtYWw7Zm9udC1zaXplOjQwcHg7bGluZS1oZWlnaHQ6MTI1JTtmb250LWZhbWlseTonLkhlbHZldGljYSBOZXVlIERlc2tJbnRlcmZhY2UnOy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246Jy5IZWx2ZXRpY2EgTmV1ZSBEZXNrSW50ZXJmYWNlLCBJdGFsaWMnO3RleHQtYWxpZ246c3RhcnQ7bGV0dGVyLXNwYWNpbmc6MHB4O3dvcmQtc3BhY2luZzowcHg7d3JpdGluZy1tb2RlOmxyLXRiO3RleHQtYW5jaG9yOnN0YXJ0O2ZpbGw6IzAwMDAwMDtmaWxsLW9wYWNpdHk6MTtzdHJva2U6bm9uZTtzdHJva2Utd2lkdGg6MXB4O3N0cm9rZS1saW5lY2FwOmJ1dHQ7c3Ryb2tlLWxpbmVqb2luOm1pdGVyO3N0cm9rZS1vcGFjaXR5OjEiCiAgICAgICB4bWw6c3BhY2U9InByZXNlcnZlIj48dHNwYW4KICAgICAgICAgeT0iMTAyOS4xMTAxIgogICAgICAgICB4PSI3LjkyMjcwMDkiCiAgICAgICAgIGlkPSJ0c3BhbjQxOTgiPmVwPC90c3Bhbj48L3RleHQ+CiAgPC9nPgo8L3N2Zz4K") 22 | .header__content 23 | p. 24 | Enhance your HTML5 progress bars with minimal effort! 25 | +githubStats('jh3y', 'ep') 26 | pre.install(style="margin: 20px 0") 27 | code.language-css(style="color: #7bff7b"). 28 | npm install @jh3y/ep 29 | hr 30 | p. 31 | The progress element sometimes doesn't play nice. It never appears consistently across browsers out of the box and different browsers throw up different limitations. Because of this, the progress element is often overlooked in favor of styled div combos. 32 | p. 33 | ep tackles this; 34 | ul 35 | li Cross browser reset and styling of HTML5 progress elements 36 | li Optional CSS helper attributes and classes 37 | li Optional JS helper for better control and interaction with progress elements. For example; to show network request status. 38 | li Plays nice in major browsers (IE10+) 39 | footer <Made real by jh3y | MIT Licensed ©2017 /> 40 | hr 41 | .features 42 | include features/standard 43 | include features/indeterminate 44 | include features/positional 45 | include features/spread 46 | include features/simulate 47 | include features/mock 48 | include features/staggered 49 | include features/timer 50 | include features/sass 51 | include features/js 52 | footer <Made real by jh3y | MIT Licensed ©2017 /> 53 | -------------------------------------------------------------------------------- /src/markup/sandbox.pug: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | body 4 | progress(id='ep', value="55", max="100") 5 | -------------------------------------------------------------------------------- /src/script/demo/index.js: -------------------------------------------------------------------------------- 1 | import 'ep-styles/ep.scss'; 2 | import './styles/demo.scss'; 3 | import 'prismjs'; 4 | 5 | import './sections/standard'; 6 | import './sections/positional'; 7 | import './sections/spread'; 8 | import './sections/simulate'; 9 | import './sections/mock'; 10 | import './sections/staggered'; 11 | import './sections/timer'; 12 | import './sections/helper'; 13 | -------------------------------------------------------------------------------- /src/script/demo/sections/helper.js: -------------------------------------------------------------------------------- 1 | import 'ep'; 2 | 3 | const el = document.getElementById('js-bar'); 4 | const myEp = new Ep(el); 5 | 6 | const FUNC = { 7 | afterSet: () => { 8 | let afterSet = () => alert('All set!'); 9 | myEp.set(23, afterSet); 10 | }, 11 | complete: () => { 12 | let onComplete = () => alert('Completed!'); 13 | myEp.complete(onComplete); 14 | }, 15 | reset: () => myEp.reset(), 16 | set: () => myEp.set(75), 17 | increase: () => myEp.increase(), 18 | decrease: () => myEp.decrease(25), 19 | simulate: () => myEp.simulate(), 20 | position: () => myEp.setPosition(['top', 'fixed']), 21 | resetPosition: () => myEp.setPosition(), 22 | spreadTrue: () => myEp.setSpread(true), 23 | spreadFalse: () => myEp.setSpread(false), 24 | ajax: () => { 25 | const makeRequest = () => { 26 | myEp.simulate(); 27 | const xhttp = new XMLHttpRequest(); 28 | xhttp.onreadystatechange = function() { 29 | if (this.readyState == 4 && this.status == 200) { 30 | myEp.complete(); 31 | } 32 | }; 33 | xhttp.open('GET', '/index.html', true); 34 | xhttp.send(); 35 | } 36 | (myEp._VALUE) ? myEp.set(0, makeRequest) : makeRequest(); 37 | } 38 | }; 39 | 40 | const jsDemo = document.querySelector('.demo--js'); 41 | jsDemo.addEventListener('click', (e) => { 42 | if (e.target.tagName === 'BUTTON') { 43 | FUNC[e.target.className](); 44 | } 45 | }) 46 | 47 | 48 | window.myEp = myEp; 49 | -------------------------------------------------------------------------------- /src/script/demo/sections/mock.js: -------------------------------------------------------------------------------- 1 | import { updateAndSet } from '../utils'; 2 | 3 | const mockBar = document.getElementById('mock-bar'); 4 | const setMock = document.getElementById('mock-set'); 5 | const mockMarkup = document.getElementById('mock-markup'); 6 | const changeMock = (e) => { 7 | const attr = 'data-mock'; 8 | updateAndSet(e.target, attr, mockMarkup); 9 | } 10 | 11 | 12 | setMock.addEventListener('input', changeMock); 13 | -------------------------------------------------------------------------------- /src/script/demo/sections/positional.js: -------------------------------------------------------------------------------- 1 | const positionalBar = document.getElementById('positional-bar'); 2 | const setPosition = document.querySelectorAll('[name=position]'); 3 | const positionalMarkup = document.getElementById('positional-markup'); 4 | 5 | const changePosition = (e) => { 6 | const oldVal = (positionalBar.className.trim() === '') ? '' : positionalBar.className; 7 | positionalBar.className = e.target.value; 8 | 9 | const newVal = (e.target.value.trim() === '') ? '' : e.target.value; 10 | positionalMarkup.innerHTML = positionalMarkup.innerHTML.replace(oldVal, newVal); 11 | } 12 | for (let i = 0; i < setPosition.length; i++) { 13 | setPosition[i].addEventListener('change', changePosition); 14 | } 15 | -------------------------------------------------------------------------------- /src/script/demo/sections/simulate.js: -------------------------------------------------------------------------------- 1 | import { toggleComplete } from '../utils'; 2 | 3 | const simulateComplete = document.getElementById('complete-simulate'); 4 | const simulateMarkup = document.getElementById('simulate-markup'); 5 | 6 | const toggleMarkup = () => { 7 | const cl = simulateMarkup.className; 8 | simulateMarkup.className = (cl.indexOf('complete') === -1) ? 'language-html demo__markup demo__markup--show-complete' : 'language-html demo__markup'; 9 | } 10 | 11 | simulateComplete.addEventListener('click', (e) => { 12 | toggleComplete(e); 13 | toggleMarkup(); 14 | }); 15 | -------------------------------------------------------------------------------- /src/script/demo/sections/spread.js: -------------------------------------------------------------------------------- 1 | import { toggleComplete, setBarAndMarkup } from '../utils'; 2 | 3 | const spreadBar = document.getElementById('spread-bar'); 4 | const spreadSet = document.getElementById('spread-set'); 5 | const spreadComplete = document.getElementById('spread-complete'); 6 | const spreadMarkup = document.getElementById('spread-markup'); 7 | const setStandard = () => { 8 | setBarAndMarkup(spreadSet, spreadBar, spreadMarkup); 9 | }; 10 | 11 | const toggleMarkup = () => { 12 | const cl = spreadMarkup.className; 13 | spreadMarkup.className = (cl.indexOf('complete') === -1) ? 'language-html demo__markup demo__markup--show-complete' : 'language-html demo__markup'; 14 | } 15 | 16 | spreadComplete.addEventListener('click', (e) => { 17 | toggleComplete(e); 18 | toggleMarkup(); 19 | }); 20 | 21 | spreadSet.addEventListener('input', setStandard); 22 | -------------------------------------------------------------------------------- /src/script/demo/sections/staggered.js: -------------------------------------------------------------------------------- 1 | import { updateAndSet } from '../utils'; 2 | 3 | const staggeredBar = document.getElementById('staggered-bar'); 4 | const setMock = document.getElementById('staggered-set'); 5 | const staggeredMarkup = document.getElementById('staggered-markup'); 6 | const changeMock = (e) => { 7 | const attr = 'data-staggered-mock'; 8 | updateAndSet(e.target, attr, staggeredMarkup); 9 | } 10 | 11 | 12 | setMock.addEventListener('input', changeMock); 13 | -------------------------------------------------------------------------------- /src/script/demo/sections/standard.js: -------------------------------------------------------------------------------- 1 | import { toggleComplete, setBarAndMarkup } from '../utils'; 2 | 3 | /** 4 | * handle basic demo interaction 5 | */ 6 | const standardBar = document.getElementById('standard-bar'); 7 | const standardSet = document.getElementById('standard-set'); 8 | const standardComplete = document.getElementById('standard-complete'); 9 | const standardMarkup = document.getElementById('standard-markup'); 10 | const setStandard = () => { 11 | setBarAndMarkup(standardSet, standardBar, standardMarkup); 12 | }; 13 | 14 | const toggleMarkup = () => { 15 | const cl = standardMarkup.className; 16 | standardMarkup.className = (cl.indexOf('complete') === -1) ? 'language-html demo__markup demo__markup--show-complete' : 'language-html demo__markup'; 17 | } 18 | 19 | standardComplete.addEventListener('click', (e) => { 20 | toggleComplete(e); 21 | toggleMarkup(); 22 | }); 23 | 24 | standardSet.addEventListener('input', setStandard); 25 | -------------------------------------------------------------------------------- /src/script/demo/sections/timer.js: -------------------------------------------------------------------------------- 1 | import { updateAndSet } from '../utils'; 2 | 3 | const timerBar = document.getElementById('timer-bar'); 4 | const setMock = document.getElementById('timer-set'); 5 | const timerMarkup = document.getElementById('timer-markup'); 6 | const timerPause = document.getElementById('pause-timer'); 7 | 8 | const changeMock = (e) => { 9 | const attr = 'data-timer'; 10 | updateAndSet(e.target, attr, timerMarkup); 11 | } 12 | 13 | const toggleMarkup = () => { 14 | const cl = timerMarkup.className; 15 | timerMarkup.className = (cl.indexOf('complete') === -1) ? 'language-html demo__markup demo__markup--show-complete' : 'language-html demo__markup'; 16 | } 17 | 18 | timerPause.addEventListener('click', (e) => { 19 | toggleMarkup(); 20 | if (timerBar.getAttribute('data-pause')) { 21 | timerBar.removeAttribute('data-pause'); 22 | } else { 23 | timerBar.setAttribute('data-pause', true); 24 | } 25 | }); 26 | 27 | setMock.addEventListener('input', changeMock); 28 | -------------------------------------------------------------------------------- /src/script/demo/styles/base.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * base styling 3 | */ 4 | 5 | * { 6 | box-sizing: border-box; 7 | } 8 | 9 | pre { 10 | padding: 10px !important; 11 | display: inline-block; 12 | border-radius: 6px; 13 | } 14 | 15 | body { 16 | font-family: 'Lato', sans-serif; 17 | background: #fafafa; 18 | padding: 15px; 19 | } 20 | 21 | fieldset { 22 | display: inline-block; 23 | } 24 | 25 | a { 26 | text-decoration: none; 27 | cursor: pointer; 28 | color: #ff7b7b; 29 | } 30 | 31 | $btn-color: rgba(123, 123, 432, 1); 32 | button { 33 | color: #fafafa; 34 | background: $btn-color; 35 | border-radius: 4px; 36 | border: none; 37 | cursor: pointer; 38 | padding: 8px 12px; 39 | margin: 4px; 40 | 41 | &:hover { 42 | background: darken($btn-color, 15%); 43 | } 44 | 45 | &:active { 46 | background: darken($btn-color, 30%); 47 | } 48 | } 49 | 50 | code { 51 | color: $btn-color; 52 | } 53 | 54 | .header__content { 55 | footer { 56 | display: none; 57 | } 58 | 59 | @media(min-width: 960px) { 60 | footer { 61 | display: block; 62 | } 63 | } 64 | } 65 | 66 | footer { 67 | text-align: center; 68 | font-size: .75rem; 69 | padding: 30px 0; 70 | 71 | @media(min-width: 960px) { 72 | display: none; 73 | } 74 | } 75 | 76 | hr { 77 | margin: 4rem 0; 78 | @media(min-width: 960px) { 79 | display: none; 80 | } 81 | } 82 | 83 | pre { 84 | max-width: 100%; 85 | overflow: auto; 86 | 87 | @media(max-width: 420px) { 88 | font-size: .75rem; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/script/demo/styles/demo.scss: -------------------------------------------------------------------------------- 1 | @import '~normalize.css'; 2 | @import '~prismjs/themes/prism-tomorrow.css'; 3 | @import url('https://fonts.googleapis.com/css?family=Lato'); 4 | 5 | @import 'base'; 6 | 7 | $brand-color: rgba(123, 123, 432, .5); 8 | 9 | .header { 10 | text-align: center; 11 | -ms-overflow-style: -ms-autohiding-scrollbar; 12 | 13 | 14 | &__content { 15 | clear: both; 16 | } 17 | 18 | @media(min-width: 960px) { 19 | position: fixed; 20 | width: 320px; 21 | top: 0; 22 | bottom: 0; 23 | padding: 20px; 24 | overflow: auto; 25 | } 26 | 27 | ul { 28 | text-align: center; 29 | li { 30 | margin: 10px 0; 31 | } 32 | } 33 | } 34 | 35 | .content { 36 | max-width: 960px; 37 | margin : 0 auto; 38 | } 39 | 40 | .features { 41 | @media(min-width: 960px) { 42 | width: 500px; 43 | float: right; 44 | } 45 | } 46 | 47 | .install { 48 | text-align: center; 49 | } 50 | 51 | .demo { 52 | padding: 10px 0; 53 | margin-bottom: 40px; 54 | } 55 | 56 | .demo-control-group { 57 | margin-bottom: 10px; 58 | 59 | label { 60 | margin-right: 8px; 61 | } 62 | } 63 | 64 | .demo__markup { 65 | .markup--complete { 66 | display: none; 67 | } 68 | &--show-complete { 69 | .markup--complete { 70 | display: block; 71 | } 72 | code:not(.markup--complete) { 73 | display: none; 74 | } 75 | } 76 | } 77 | 78 | .demo--positional { 79 | position: relative; 80 | } 81 | 82 | $indeterminate-fg: #ff7b7b; 83 | .demo-bar { 84 | &--indeterminate { 85 | &:indeterminate { 86 | background: $indeterminate-fg; 87 | 88 | &::-moz-progress-bar { 89 | background: $indeterminate-fg; 90 | } 91 | 92 | &::-webkit-progress-bar { 93 | background: $indeterminate-fg; 94 | } 95 | } 96 | 97 | } 98 | } 99 | 100 | .demo--js .demo-control-group { 101 | clear: both; 102 | 103 | pre { 104 | float: left; 105 | } 106 | 107 | button { 108 | margin-top: .5em; 109 | } 110 | } 111 | 112 | #timer-bar, 113 | #staggered-bar, 114 | #mock-bar, 115 | #simulate-bar { 116 | animation-iteration-count: infinite !important; 117 | } 118 | 119 | @import '~ep-styles/mixins'; 120 | #standard-bar, 121 | #js-bar { 122 | @include color-bar(#7bff7b); 123 | } 124 | 125 | #positional-bar { 126 | @include color-bar(linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet, red, red, red, red, red)); 127 | } 128 | 129 | #mock-bar { 130 | @include color-bar(repeating-linear-gradient(45deg, #ff7b7b, #ff7b7b 10px, black 10px, black 20px)); 131 | } 132 | 133 | #staggered-bar { 134 | @include color-bar(repeating-linear-gradient(-45deg, #7bff7b, #7bff7b 10px, black 10px, black 20px)); 135 | } 136 | 137 | #mock-bar, 138 | #top-bar, 139 | #simulate-bar { 140 | @include color-bar(repeating-linear-gradient(45deg, #7b7bff, #7b7bff 10px, black 10px, black 20px)); 141 | } 142 | 143 | #spread-bar, 144 | #timer-bar { 145 | @include color-bar(#7b7bff); 146 | } 147 | -------------------------------------------------------------------------------- /src/script/demo/utils.js: -------------------------------------------------------------------------------- 1 | const toggleComplete = function(e) { 2 | const bar = document.getElementById(e.target.getAttribute('data-for')); 3 | const setButtonText = () => { 4 | e.target.innerText = (e.target.innerText === 'Complete progress') ? 'Reset progress' : 'Complete progress'; 5 | bar.removeEventListener('transitionend', setButtonText); 6 | } 7 | if (bar.getAttribute('data-complete')) { 8 | bar.addEventListener('transitionend', setButtonText); 9 | bar.removeAttribute('data-complete'); 10 | } else { 11 | bar.addEventListener('transitionend', setButtonText); 12 | bar.setAttribute('data-complete', true); 13 | } 14 | } 15 | 16 | const setBarAndMarkup = function(input, bar, markup) { 17 | if (input.value.trim() === '') return; 18 | const oldVal = bar.getAttribute('value'); 19 | bar.value = input.value; 20 | markup.innerHTML = markup.innerHTML.replace(new RegExp(oldVal, 'g'), input.value); 21 | }; 22 | 23 | const updateAndSet = function(el, attr, markup) { 24 | const newVal = el.value; 25 | if (newVal.trim() === '') return; 26 | const bar = document.getElementById(el.getAttribute('data-for')); 27 | const oldVal = bar.getAttribute(attr); 28 | bar.setAttribute(attr, newVal); 29 | markup.innerHTML = markup.innerHTML.replace(new RegExp(oldVal, 'g'), newVal); 30 | }; 31 | 32 | export { toggleComplete, setBarAndMarkup, updateAndSet }; 33 | -------------------------------------------------------------------------------- /src/script/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ep - enhance your progress bars with minimal effort 3 | * 4 | * https://jh3y.github.io/ep 5 | * @license MIT 6 | * @author jh3y 7 | * @version 2.0.6 8 | * (c) 2017 9 | */ 10 | (function() { 11 | /** 12 | * Class that acts as a wrapper for interacting with HTML5 element 13 | */ 14 | class Ep { 15 | /** 16 | * Create interface with element 17 | * @param {HTMLProgressElement} el - the element. 18 | */ 19 | constructor(el) { 20 | /* If not a element, throw an error */ 21 | if (!el || Object.getPrototypeOf(el) !== HTMLProgressElement.prototype) 22 | throw Error('ep: you must pass a element instance'); 23 | /* Set internal reference and starting value */ 24 | this._EL = el; 25 | this._VALUE = parseInt(el.getAttribute('value'), 10) || 0; 26 | } 27 | 28 | /** 29 | * Sets value with optional callback for when transition 30 | * finishes 31 | * 32 | * @param {number} val - the new value to be set 33 | * @param {function} cb - an optional callback that will be invoked on 34 | * transitionend 35 | * @returns {undefined} 36 | */ 37 | set(val, cb) { 38 | /* Santize values so that value can't be set out of 0-100 range */ 39 | const sanitizeValue = (val, min = 0, max = 100) => { 40 | let sanitized = val; 41 | if (sanitized > max) sanitized = max; 42 | if (sanitized < min) sanitized = min; 43 | return sanitized; 44 | }; 45 | const sVal = sanitizeValue(val); 46 | if (typeof sVal === 'number' && sVal >= 0 && sVal <= 100) { 47 | const hasCb = cb && typeof cb === 'function'; 48 | /* If a callback has been defined then hook into transitionend event */ 49 | if (hasCb) { 50 | const onSetEnd = () => { 51 | cb(); 52 | this._EL.removeEventListener('transitionend', onSetEnd); 53 | }; 54 | this._EL.addEventListener('transitionend', onSetEnd); 55 | } 56 | /* Set the new value and update the internal reference */ 57 | this._EL.setAttribute('value', sVal); 58 | this._VALUE = sVal; 59 | } 60 | } 61 | /** 62 | * Sets the spread class on the element. The default is to remove 63 | * it if present 64 | * 65 | * @param {bool} spread - whether to set spread class or not 66 | * @returns {undefined} 67 | */ 68 | setSpread(spread = false) { 69 | const cl = this._EL.classList; 70 | (spread) ? cl.add('ep--spread') : cl.remove('ep--spread'); 71 | } 72 | /** 73 | * Sets the indeterminate class on the element. 74 | * NOTE:; It's only necessary to use this when dealing with browsers that 75 | * struggle with indeterminate elements such as iOS Safari 76 | * 77 | * As with the "setSpread" method, the default behavior is to remove the 78 | * class 79 | * 80 | * @param {bool} indeterminate - whether to set indeterminate class or not 81 | * @returns {undefined} 82 | */ 83 | setIndeterminate(indeterminate = false) { 84 | const cl = this._EL.classList; 85 | const indeterminateCL = 'ep--indeterminate'; 86 | (indeterminate) ? cl.add(indeterminateCL) : cl.remove(indeterminateCL); 87 | } 88 | /** 89 | * Toggles pause attribute helper. This will pause any animations taking 90 | * place such as those for timers and mocks. 91 | * @returns {undefined} 92 | */ 93 | togglePause() { 94 | if (this._EL.getAttribute('data-pause')) 95 | this._EL.removeAttribute('data-pause'); 96 | else 97 | this._EL.setAttribute('data-pause', true); 98 | } 99 | /** 100 | * Sets positional helper classes on element from given Array 101 | * 102 | * @param {Array} posArr - array of positions to be set from top, fixed, 103 | * bottom 104 | * @returns {undefined} 105 | */ 106 | setPosition(posArr) { 107 | const positions = ['top', 'fixed', 'bottom']; 108 | for (let p = 0; p < positions.length; p++) 109 | for (let cl = 0; cl < this._EL.classList.length; cl++) 110 | if (this._EL.classList[cl] === `ep--${positions[p]}`) 111 | this._EL.classList.remove(`ep--${positions[p]}`); 112 | if (posArr && posArr.length) 113 | posArr.forEach((pos) => this._EL.classList.add(`ep--${pos}`)); 114 | } 115 | /** 116 | * Helper function to increase value by a value 117 | * the default is 5 which can be overridden. Hook into the post set by 118 | * passing a callback 119 | * 120 | * @param {number} amount - amount to increase value by 121 | * @param {function} cb - optional callback function to be fired on complete 122 | * @returns {undefined} 123 | */ 124 | increase(amount = 5, cb) { 125 | this.set(this._VALUE + amount, cb); 126 | } 127 | /** 128 | * Helper function to decrease value by a value 129 | * the default is 5 which can be overridden. Hook into the post set by 130 | * passing a callback 131 | * 132 | * @param {number} amount - amount to decrease value by 133 | * @param {function} cb - optional callback function to be fired on complete 134 | * @returns {undefined} 135 | */ 136 | decrease(amount = 5, cb) { 137 | this.set(this._VALUE - amount, cb); 138 | } 139 | /** 140 | * Resets element setting value to 0, removing any attributes and 141 | * simulations 142 | * @returns {undefined} 143 | */ 144 | reset() { 145 | if (this._SIMULATING) clearInterval(this._SIMULATING); 146 | this.set(0); 147 | this._EL.removeAttribute('data-complete'); 148 | this._EL.removeAttribute('data-pause'); 149 | setTimeout(() => this._EL.removeAttribute('style'), 0); 150 | } 151 | /** 152 | * Provides a way to quickly mock a bar progress. 153 | * Duration is restricted by whatever has been generated on the CSS side. 154 | * 155 | * For instance, if I set a duration of 1000. Nothing would happen as the CSS 156 | * has no rule for [data-mock="1000"]. 157 | * 158 | * As with setting you can hook into the ending with a callback parameter. 159 | * 160 | * @param {number} duration - duration of mock in seconds 161 | * @param {bool} staggered - whether mock is staggered (determined by 162 | * keyframes animation) 163 | * @param {function} cb - optional callback function for when mock animation 164 | * is complete 165 | * @returns {undefined} 166 | */ 167 | mock(duration = 4, staggered, cb) { 168 | const attr = (staggered) ? 'data-staggered-mock' : 'data-mock'; 169 | const onMockEnd = () => { 170 | this._EL.removeAttribute(attr); 171 | if (cb && typeof cb === 'function') cb(); 172 | this._EL.removeEventListener('animationend', onMockEnd); 173 | }; 174 | this._EL.setAttribute(attr, duration); 175 | this._EL.addEventListener('animationend', onMockEnd); 176 | } 177 | /** 178 | * Display element as a timer decreasing from full value to 0 179 | * 180 | * Much like mocking, restrained by what CSS rules have been generated. 181 | * 182 | * @param {number} duration - time in seconds for timer to complete 183 | * @param {function} cb - optional callback to be invoked when timer 184 | * completes 185 | * @returns {undefined} 186 | */ 187 | time(duration = 4, cb) { 188 | const onTimerEnd = () => { 189 | this._EL.removeAttribute('data-timer'); 190 | if (cb && typeof cb === 'function') cb(); 191 | this._EL.removeEventListener('animationend', onTimerEnd); 192 | }; 193 | this._EL.setAttribute('data-timer', duration); 194 | this._EL.addEventListener('animationend', onTimerEnd); 195 | } 196 | /** 197 | * Simulate a elements progress by having it increase in small 198 | * increments at a given interval until a value is reached. 199 | * 200 | * Handy when you want to simulate progress for something that doesn't have 201 | * good transparency. For example; you can hook into the success callback 202 | * of an AJAX request and set the element as complete whilst it 203 | * is simulating 204 | * 205 | * @param {number} step - ms interval for value increments to be applied 206 | * @param {number} increment - increment value to be increased at step 207 | * @param {number} max - max value to be reached before increments cease 208 | * @returns {undefined} 209 | */ 210 | simulate(step = 1000, increment = 5, max = 99) { 211 | this._SIMULATING = setInterval(() => { 212 | const modMax = max % this._VALUE; 213 | const checkMod = !this._VALUE || modMax > increment || modMax === 0; 214 | const increaseVal = (checkMod) ? increment : modMax; 215 | if (this._VALUE !== max) 216 | this.increase(increaseVal); 217 | else 218 | clearInterval(this._SIMULATING); 219 | }, step); 220 | } 221 | /** 222 | * Completes progress by setting a value to 100 and then resetting 223 | * it back to 0 224 | * 225 | * Users can hook into this on complete by passing a callback 226 | * 227 | * @param {function} cb - optional callback for progress completion 228 | * @returns {undefined} 229 | */ 230 | complete(cb) { 231 | const onComplete = () => { 232 | this._EL.style.transitionDuration = '0s'; 233 | this.reset(); 234 | this._EL.removeEventListener('transitionend', onComplete); 235 | if (cb && typeof cb === 'function') cb(); 236 | }; 237 | this.set(100); 238 | this._EL.setAttribute('data-complete', true); 239 | this._EL.addEventListener('transitionend', onComplete); 240 | } 241 | } 242 | window.Ep = Ep; 243 | })(); 244 | -------------------------------------------------------------------------------- /src/styles/_configuration.scss: -------------------------------------------------------------------------------- 1 | // ep configruation variables 2 | $ep-ns: ep !default; 3 | 4 | // Aesthetics 5 | $ep-height: 6px !default; 6 | $ep-fg: #3498db !default; 7 | $ep-indeterminate-fg: $ep-fg !default; 8 | $ep-opacity: .6 !default; 9 | $ep-radius: 0 !default; 10 | $ep-transition: .25s !default; 11 | 12 | // Feature constraints 13 | $ep-timeout-threshold: 30 !default; 14 | $ep-mocks-min: 1 !default; 15 | $ep-mocks-max: 4 !default; 16 | $ep-staggered-mocks-min: 1 !default; 17 | $ep-staggered-mocks-max: 4 !default; 18 | $ep-timers-min: 1 !default; 19 | $ep-timers-max: 4 !default; 20 | $ep-simulate-max: 99 !default; 21 | -------------------------------------------------------------------------------- /src/styles/_mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin color-bar($color) { 2 | background: $color; 3 | color: $color; 4 | 5 | &::-moz-progress-bar { 6 | background: $color; 7 | } 8 | 9 | &::-webkit-progress-bar { 10 | background: $color; 11 | } 12 | 13 | &::-webkit-progress-value { 14 | background: $color; 15 | } 16 | } 17 | 18 | @mixin indeterminate { 19 | animation: ep-indeterminate 2s infinite linear; 20 | background: $ep-indeterminate-fg; 21 | // hides the indeterminate blocks animation in IE 22 | color: transparent; 23 | width: 100%; 24 | 25 | &::-moz-progress-bar { 26 | background: $ep-indeterminate-fg; 27 | } 28 | 29 | &::-webkit-progress-bar { 30 | background: $ep-indeterminate-fg; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/styles/core.scss: -------------------------------------------------------------------------------- 1 | @import 'configuration'; 2 | @import 'mixins'; 3 | 4 | @mixin create-core { 5 | @for $percent from 1 through 100 { 6 | &[value='#{$percent}'] { 7 | width: $percent * 1%; 8 | } 9 | } 10 | } 11 | 12 | @keyframes ep-indeterminate { 13 | 0% { opacity: 0; } 14 | 50% { opacity: $ep-opacity; } 15 | 100% { opacity: 0; } 16 | } 17 | 18 | progress { 19 | appearance: none; 20 | border: 0; 21 | height: $ep-height; 22 | opacity: $ep-opacity; 23 | position: relative; 24 | transition: width $ep-transition ease 0s, visibility ($ep-transition * 2) ease 0s; 25 | width: 0; 26 | 27 | // Remove annoying marker border in IE 28 | &::-ms-fill { 29 | border: 0; 30 | } 31 | 32 | @include color-bar($ep-fg); 33 | 34 | &:indeterminate { 35 | @include indeterminate; 36 | } 37 | 38 | @include create-core; 39 | } 40 | -------------------------------------------------------------------------------- /src/styles/ep.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * ep - enhance your progress bars with minimal effort 3 | * 4 | * https://jh3y.github.io/ep 5 | * @license MIT 6 | * @author jh3y 7 | * @version 2.0.6 8 | * (c) 2017 9 | */ 10 | @import 'core'; 11 | @import './features/mock'; 12 | @import './features/staggered'; 13 | @import './features/timer'; 14 | @import './features/simulate'; 15 | @import './features/helpers'; 16 | -------------------------------------------------------------------------------- /src/styles/features/helpers.scss: -------------------------------------------------------------------------------- 1 | @import '../configuration'; 2 | 3 | progress { 4 | &[data-complete] { 5 | animation: none; 6 | visibility: hidden; 7 | width: 100%; 8 | } 9 | 10 | &[data-pause] { 11 | animation-play-state: paused; 12 | } 13 | 14 | &.#{$ep-ns} { 15 | 16 | &--top { 17 | left: 0; 18 | position: absolute; 19 | top: 0; 20 | } 21 | 22 | &--bottom { 23 | bottom: 0; 24 | left: 0; 25 | position: absolute; 26 | } 27 | 28 | &--fixed { 29 | position: fixed; 30 | } 31 | 32 | &--spread { 33 | left: 50%; 34 | transform: translateX(-50%); 35 | } 36 | 37 | &--indeterminate { 38 | @include indeterminate; 39 | } 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/styles/features/mock.scss: -------------------------------------------------------------------------------- 1 | @import '../configuration'; 2 | 3 | @keyframes ep-mock { 4 | to { width: 100%; } 5 | } 6 | 7 | progress { 8 | @for $duration from $ep-mocks-min through $ep-mocks-max { 9 | 10 | &[data-mock] { 11 | animation-name: ep-mock; 12 | } 13 | 14 | &[data-mock='#{$duration}'] { 15 | animation-duration: $duration * 1s; 16 | width: 0; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/styles/features/simulate.scss: -------------------------------------------------------------------------------- 1 | @keyframes ep-simulate { 2 | from { width: 0; } 3 | } 4 | 5 | progress { 6 | &[data-simulate] { 7 | animation: ep-simulate ($ep-timeout-threshold * 1s); 8 | animation-timing-function: steps($ep-timeout-threshold); 9 | width: $ep-simulate-max * 1%; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/styles/features/staggered.scss: -------------------------------------------------------------------------------- 1 | @import '../configuration'; 2 | 3 | @keyframes ep-staggered-mock { 4 | 0% { width: 0%; } 5 | 10% { width: 10%; } 6 | 20% { width: 10%; } 7 | 30% { width: 30%; } 8 | 40% { width: 30%; } 9 | 50% { width: 30%; } 10 | 60% { width: 50%; } 11 | 70% { width: 70%; } 12 | 80% { width: 70%; } 13 | 90% { width: 90%; } 14 | 100% { width: 100%; } 15 | } 16 | 17 | progress { 18 | @for $duration from $ep-staggered-mocks-min through $ep-staggered-mocks-max { 19 | 20 | &[data-staggered-mock] { 21 | animation-name: ep-staggered-mock; 22 | } 23 | 24 | &[data-staggered-mock='#{$duration}'] { 25 | animation-duration: $duration * 1s; 26 | width: 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/styles/features/timer.scss: -------------------------------------------------------------------------------- 1 | @import '../configuration'; 2 | 3 | @keyframes ep-timer { 4 | from { width: 100%; } 5 | to { width: 0; } 6 | } 7 | 8 | progress { 9 | @for $duration from $ep-timers-min through $ep-timers-max { 10 | 11 | &[data-timer] { 12 | animation-name: ep-timer; 13 | } 14 | 15 | &[data-timer='#{$duration}'] { 16 | animation-duration: $duration * 1s; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /webpack.config.babel.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 3 | const autoprefixer = require('autoprefixer'); 4 | const webpack = require('webpack'); 5 | const path = require('path'); 6 | 7 | const IS_DIST = (process.argv.indexOf('--dist') !== -1) ? true : false; 8 | const IS_DEPLOY = (process.argv.indexOf('--deploy') !== -1) ? true : false; 9 | const STYLE_LOAD = 'css-loader!postcss-loader!sass-loader'; 10 | const STYLE_LOADER = (IS_DIST || IS_DEPLOY) ? ExtractTextPlugin.extract('style-loader', STYLE_LOAD) : `style-loader!${STYLE_LOAD}`; 11 | 12 | const config = { 13 | devServer: { 14 | port: 1987 15 | }, 16 | entry: { 17 | demo: './src/script/demo' 18 | }, 19 | output: { 20 | path: `${__dirname}/public`, 21 | filename: '[name][hash].js' 22 | }, 23 | module: { 24 | loaders: [ 25 | { 26 | test: /\.js$/, 27 | loader: 'babel', 28 | include: /(src\/script)/, 29 | query: { 30 | presets: [ 31 | 'es2015' 32 | ] 33 | } 34 | }, 35 | { 36 | test: /\.scss$/, 37 | include: /(src\/)/, 38 | loader: STYLE_LOADER 39 | }, 40 | { 41 | test: /\.pug$/, 42 | include: /(src\/markup)/, 43 | loader: 'pug' 44 | } 45 | ] 46 | }, 47 | resolve: { 48 | root: [ 49 | path.resolve('./src/script'), 50 | path.resolve('./src/styles') 51 | ], 52 | alias: { 53 | ep: path.resolve(__dirname, 'src/script'), 54 | 'ep-styles': path.resolve(__dirname, 'src/styles') 55 | }, 56 | extensions: [ '', '.js', '.styl' ] 57 | }, 58 | plugins: [ 59 | new HtmlWebpackPlugin({ 60 | template: './src/markup/index.pug', 61 | filename: 'index.html', 62 | chunks: [ 'demo' ], 63 | minify: { 64 | collapseWhitespace: true 65 | } 66 | }), 67 | (IS_DEPLOY) ? function () {} : new HtmlWebpackPlugin({ 68 | template: './src/markup/sandbox.pug', 69 | filename: 'sandbox.html', 70 | chunks: [ 'demo' ], 71 | minify: { 72 | collapseWhitespace: true 73 | } 74 | }), 75 | (IS_DEPLOY) ? new ExtractTextPlugin('[name][hash].css') : function () {}, 76 | (IS_DEPLOY) ? new webpack.optimize.UglifyJsPlugin() : function () {} 77 | ], 78 | postcss: function () { 79 | return [ autoprefixer ]; 80 | } 81 | } 82 | 83 | module.exports = config; 84 | --------------------------------------------------------------------------------