Repository: fgnass/spin.js Branch: master Commit: 653e79c5ef1f Files: 24 Total size: 60.2 KB Directory structure: gitextract_q8vr1fl3/ ├── .gitattributes ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── Gruntfile.cjs ├── LICENSE.md ├── README.md ├── SpinnerOptions.d.ts ├── package.json ├── rollup.config.js ├── site/ │ ├── CNAME │ ├── assets/ │ │ ├── gh-fork-ribbon.css │ │ ├── main.css │ │ ├── prism.css │ │ └── prism.js │ ├── example/ │ │ ├── positioning.css │ │ ├── positioning.html │ │ └── positioning.js │ ├── index.html │ ├── index.js │ └── serve.json ├── spin.css ├── spin.ts └── tsconfig.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ # so documentation files won't cause project language to be shown as HTML site/* linguist-documentation ================================================ FILE: .gitignore ================================================ /.grunt /node_modules /site/bundle.js /site/spin.css /site/spin.js /site/spin.umd.js /spin.js /spin.d.ts ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [4.1.2] - 2024-07-19 ### Changed - Removed some dead code left over from version 3.x. ## [4.1.1] - 2021-09-01 ### Changed - Set package type to `module` to better support native ES module imports. - Improved website design. ## [4.1.0] - 2019-10-22 ### Added - `spinner-line-shrink` animation preset. ## [4.0.0] - 2018-05-06 ### Changed - Switched from `requestAnimationFrame` back to CSS keyframe animations for better performance. - In order to preserve compatibility with the `style-src 'self';` Content Security Policy, animations are now defined via an external CSS file rather than inserted dynamically. Add the following to your page to use one of the preset animations: ```html ``` You can also define custom opacity animations in your own CSS file and select them via the `animation` option. ### Fixed - A transparent shadow is now set by default to resolve aliased lines in certain browsers (issue [#355]). ### Removed - `opacity` and `trail` options (define a custom opacity animation instead). - Support for Internet Explorer 9 (it doesn't support keyframe animations). - `fps` option (only used for IE 9). ## [3.1.0] - 2017-11-26 ### Added - Support for custom box-shadows with corrected positioning (issue [#35]). - `fadeColor` option to customize the color that lines fade to (issue [#30]). ## [3.0.0] - 2017-11-09 ### Added - Support for Content Security Policy `style-src 'self';` option (issue [#115] and issue [#229]). - [CONTRIBUTING.md](CONTRIBUTING.md) file. ### Changed - Rewritten in TypeScript. - Replaced dynamic CSS keyframe animations with `requestAnimationFrame`. - Unified animation logic in all browsers. - Moved internal functions out of Spinner instance. - Website now uses native range inputs rather than a polyfill, and doesn't depend on jQuery. - Distributed as a standard ES6 module (closes [#341]). ### Fixed - Janky animation appearance in Microsoft Edge (issue [#342]). - Missing line with trail set to 100 in Internet Explorer (issue [#327]). - Spinner is not defined error in Angular (issue [#340]). ### Removed - Useless `hwaccel` option. - IE 6-8 support and VML fallback since it isn't needed for IE 9+. - Minified files from bundle. - jQuery plugin (closes [#325]). - Bower/composer/component/spm support. Install from npm instead (recommended), or save spin.js file in your repo. Note: version 3.0 does not change the public Spinner API, so if you are already using a module bundler such as Webpack or Rollup, upgrading should be as easy as adding the following ES6 module import: ```javascript import {Spinner} from 'spin.js'; ``` ## [2.3.2] - 2015-07-24 ### Fixed - Updated UMD header to protect against HTMLElement global pollution (PR [#300]). ## [2.3.1] - 2015-06-15 ### Changed - There were multiple tagging issues that produced 2.1.3, 2.2.0, and 2.3.0. In the spirit of SemVer, this release is now 2.3.1. - The minified spin.min.js is now distributed in the repo, making Bower usage easier (issue [#250]). ### Fixed - Incorrect syntax in the example at the top of spin.js (PR [#294]). ### Removed * Moot `version` property from Bower manifest (PR [#295]). ## [2.1.2] - 2015-05-28 ### Changed - Version 2.1.1 had a packaging error, so this release is now 2.1.2. - Standard CSS attributes are now preferred over vendor ones if supported. - Unified internal coding style across all files. - Website now uses the latest version of jQuery from its 1.x branch. - Updated documentation to note that the container element must use relative positioning (issue [#292]). ### Added - spm support (PR [#232]). ### Fixed - All broken examples - Bug on demo page where the direction setting wasn't reflected in the option object. ## [2.1.0] - 2015-04-16 ### Added - `scale` option for resizing the spinner (PR [#287]). - Support for importing on server without DOM (PR [#283]). ## [2.0.2] - 2015-01-02 ### Fixed - Use correct `require` call in jQuery plugin ### Removed - Cleaned up unused code ## [2.0.1] - 2014-04-24 ### Fixed - Position offsets are now applied when instantiating spinner without a target (issue [#218]). ## [2.0.0] - 2014-03-13 ### Changed - Spinner is now absolutely positioned at `top: 50%, left: 50%` by default. - `top` and `left` options now require CSS units. For example, `top: 100` must instead be written as `top: '100px'`. - Spinner must now always be invoked as constructor. ## [1.3.3] - 2013-12-24 ### Changed - Created master branch and Grunt-based build process (issue [#189]). ## [1.3.2] - 2013-08-26 ### Fixed - SyntaxError in Chrome Canary (issue [#168]). ## [1.3.1] - 2013-08-19 ### Added - Support for multi-colored spinners (PR [#167]). ## [1.3.0] - 2013-04-02 ### Added - `direction` option to control the spinning direction (PR [#126]). - jQuery plugin ### Changed - Implemented UMD pattern to support AMD and CommonJS module loaders - Use strict mode ## [1.2.8] - 2013-02-07 ### Fixed - 'Spinner' is undefined error in Internet Explorer 7 and 8 (issue [#78]). ## [1.2.7] - 2012-10-02 ### Added - `position` option to control the corresponding CSS property (issue [#98]). - Trailing semicolon to support concatenation tools that don't know about ASI (issue [#96] and issue [#99]). ## [1.2.6] - 2012-08-30 ### Added - `corners` option to control the border-radius (issue [#93]). ### Fixed - Scroll bar appearing in wide target elements (issue [#74]). - Invalid Argument error in Internet Explorer (issue [#77]). - Broken spinner in Opera 12 (issue [#87]). - Unexpected positioning when specifying `top` and `left` options as a string (issue [#81] and issue [#90]). ## [1.2.5] - 2012-03-22 ### Added - `rotate` option (issue [#60]). ### Fixed - Bug that prevented the VML from being displayed when Modernizr's html5shiv was used (issue [#58]). - The `constructor` property is now preserved (issue [#61]). ## [1.2.4] - 2012-02-28 ### Added - New config options: `top`, `left`, `zIndex`, and `className`. ## [1.2.3] - 2012-01-30 ### Changed - Disabled hardware acceleration by default to prevent disappearing objects and flashing colors in Chrome and Safari (issue [#41] and issue [#47]). ## [1.2.2] - 2011-11-08 ### Fixed - Cross-domain issue with the dynamically created stylesheet (issue [#36]). ## [1.2.1] - 2011-10-05 ### Fixed - Error when loading spinner in Internet Explorer 9 (issue [#31]). ## [1.2.0] - 2011-09-16 ### Changed - Calling `spin()` now invokes `stop()` first (issue [#28]). - The `new` operator is now optional (issue [#14]). - Improved accessibility by adding `role="progressbar"`. ### Fixed - Implemented workaround for negative margin bug in Internet Explorer (issue [#27]). ## [1.1.0] - 2011-09-06 ### Changed - Optimized the code for gzip compression. While the minified version got slightly larger, the zipped version now only weighs 1.7K. ### Fixed - Animation occasionally got out of sync in mobile Safari and Android's built-in WebKit (issue [#12]). - Spinner was misplaced when the target element had a non-zero padding (issue [#23]). ## [1.0.0] - 2011-08-16 - Initial release [4.1.2]: https://github.com/fgnass/spin.js/compare/4.1.1...4.1.2 [4.1.1]: https://github.com/fgnass/spin.js/compare/4.1.0...4.1.1 [4.1.0]: https://github.com/fgnass/spin.js/compare/4.0.0...4.1.0 [4.0.0]: https://github.com/fgnass/spin.js/compare/3.1.0...4.0.0 [3.1.0]: https://github.com/fgnass/spin.js/compare/3.0.0...3.1.0 [3.0.0]: https://github.com/fgnass/spin.js/compare/2.3.2...3.0.0 [2.3.2]: https://github.com/fgnass/spin.js/compare/2.3.1...2.3.2 [2.3.1]: https://github.com/fgnass/spin.js/compare/2.1.2...2.3.1 [2.1.2]: https://github.com/fgnass/spin.js/compare/2.1.0...2.1.2 [2.1.0]: https://github.com/fgnass/spin.js/compare/2.0.2...2.1.0 [2.0.2]: https://github.com/fgnass/spin.js/compare/2.0.1...2.0.2 [2.0.1]: https://github.com/fgnass/spin.js/compare/2.0.0...2.0.1 [2.0.0]: https://github.com/fgnass/spin.js/compare/1.3.3...2.0.0 [1.3.3]: https://github.com/fgnass/spin.js/compare/1.3.2...1.3.3 [1.3.2]: https://github.com/fgnass/spin.js/compare/1.3.1...1.3.2 [1.3.1]: https://github.com/fgnass/spin.js/compare/1.3.0...1.3.1 [1.3.0]: https://github.com/fgnass/spin.js/compare/1.2.8...1.3.0 [1.2.8]: https://github.com/fgnass/spin.js/compare/1.2.7...1.2.8 [1.2.7]: https://github.com/fgnass/spin.js/compare/1.2.6...1.2.7 [1.2.6]: https://github.com/fgnass/spin.js/compare/1.2.5...1.2.6 [1.2.5]: https://github.com/fgnass/spin.js/compare/1.2.4...1.2.5 [1.2.4]: https://github.com/fgnass/spin.js/compare/1.2.3...1.2.4 [1.2.3]: https://github.com/fgnass/spin.js/compare/1.2.2...1.2.3 [1.2.2]: https://github.com/fgnass/spin.js/compare/1.2.1...1.2.2 [1.2.1]: https://github.com/fgnass/spin.js/compare/1.2.0...1.2.1 [1.2.0]: https://github.com/fgnass/spin.js/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/fgnass/spin.js/compare/1.0.0...1.1.0 [1.0.0]: https://github.com/fgnass/spin.js/tree/1.0.0 [#355]: https://github.com/fgnass/spin.js/issues/355 [#342]: https://github.com/fgnass/spin.js/issues/342 [#341]: https://github.com/fgnass/spin.js/issues/341 [#340]: https://github.com/fgnass/spin.js/issues/340 [#327]: https://github.com/fgnass/spin.js/issues/327 [#325]: https://github.com/fgnass/spin.js/issues/325 [#300]: https://github.com/fgnass/spin.js/pull/300 [#295]: https://github.com/fgnass/spin.js/pull/295 [#294]: https://github.com/fgnass/spin.js/pull/294 [#292]: https://github.com/fgnass/spin.js/issues/292 [#287]: https://github.com/fgnass/spin.js/pull/287 [#283]: https://github.com/fgnass/spin.js/pull/283 [#250]: https://github.com/fgnass/spin.js/issues/250 [#232]: https://github.com/fgnass/spin.js/pull/232 [#229]: https://github.com/fgnass/spin.js/issues/229 [#218]: https://github.com/fgnass/spin.js/issues/218 [#189]: https://github.com/fgnass/spin.js/issues/189 [#168]: https://github.com/fgnass/spin.js/issues/168 [#167]: https://github.com/fgnass/spin.js/pull/167 [#126]: https://github.com/fgnass/spin.js/pull/126 [#115]: https://github.com/fgnass/spin.js/issues/115 [#99]: https://github.com/fgnass/spin.js/issues/99 [#98]: https://github.com/fgnass/spin.js/issues/98 [#96]: https://github.com/fgnass/spin.js/issues/96 [#93]: https://github.com/fgnass/spin.js/issues/93 [#90]: https://github.com/fgnass/spin.js/issues/90 [#87]: https://github.com/fgnass/spin.js/issues/87 [#81]: https://github.com/fgnass/spin.js/issues/81 [#78]: https://github.com/fgnass/spin.js/issues/78 [#77]: https://github.com/fgnass/spin.js/issues/77 [#74]: https://github.com/fgnass/spin.js/issues/74 [#61]: https://github.com/fgnass/spin.js/issues/61 [#60]: https://github.com/fgnass/spin.js/issues/60 [#58]: https://github.com/fgnass/spin.js/issues/58 [#47]: https://github.com/fgnass/spin.js/issues/47 [#41]: https://github.com/fgnass/spin.js/issues/41 [#36]: https://github.com/fgnass/spin.js/issues/36 [#35]: https://github.com/fgnass/spin.js/issues/35 [#31]: https://github.com/fgnass/spin.js/issues/31 [#30]: https://github.com/fgnass/spin.js/issues/30 [#28]: https://github.com/fgnass/spin.js/issues/28 [#27]: https://github.com/fgnass/spin.js/issues/27 [#23]: https://github.com/fgnass/spin.js/issues/23 [#14]: https://github.com/fgnass/spin.js/issues/14 [#12]: https://github.com/fgnass/spin.js/issues/12 ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing After cloning the repository, run `npm install` to install development dependencies and compile the project. Then run `npm start` to start a static file server for viewing the site. After editing any TypeScript files, run `npm run prepare` to compile TypeScript and create a new bundle for the site. ## Workflow to release a new version Run `npm version [ | major | minor | patch]` to bump the package version and create a new version commit and tag. Then run `npm publish` to build and publish the new version. Finally, run `npm run gh-pages` and `git push --tags` to update the website and repository. ================================================ FILE: Gruntfile.cjs ================================================ module.exports = function(grunt) { grunt.initConfig({ copy: { js: { files: [ { src: ['spin.js', 'spin.css'], dest: 'site/' } ] } }, 'gh-pages': { release: { options: { base: 'site', message: 'automatic commit' }, src: '**/*' } } }); grunt.loadNpmTasks('grunt-contrib-copy'); grunt.loadNpmTasks('grunt-gh-pages'); grunt.registerTask('default', ['copy']); }; ================================================ FILE: LICENSE.md ================================================ The MIT License =============== Copyright (c) 2011-2018 Felix Gnass [fgnass at gmail dot com] 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. ================================================ FILE: README.md ================================================ # spin.js [![JS.ORG](https://img.shields.io/badge/js.org-spin-ffb400.svg?style=flat-square)](http://js.org) An animated loading spinner * No images * No dependencies * Highly configurable * Resolution independent * Uses CSS keyframe animations * Works in all major browsers * Includes TypeScript definitions * Distributed as a native ES6 module * MIT License ## Installation `npm install spin.js` ## Usage ### CSS ```html ``` ### TypeScript or JavaScript ```javascript import {Spinner} from 'spin.js'; var target = document.getElementById('foo'); new Spinner({color:'#fff', lines: 12}).spin(target); ``` For an interactive demo and a list of all supported options please refer to the [project's homepage](https://spin.js.org). ================================================ FILE: SpinnerOptions.d.ts ================================================ export interface SpinnerOptions { /** * The number of lines to draw */ lines?: number; /** * The length of each line */ length?: number; /** * The line thickness */ width?: number; /** * The radius of the inner circle */ radius?: number; /** * Scales overall size of the spinner */ scale?: number; /** * Corner roundness (0..1) */ corners?: number; /** * A CSS color string, or array of strings to set the line color */ color?: string | string[]; /** * A CSS color string, or array of strings to set the color that lines will fade to. * Defaults to transparent. */ fadeColor?: string | string[]; /** * The animation name used for the spinner lines. Defaults to 'spinner-line-fade-default'. */ animation?: string; /** * The rotation offset */ rotate?: number; /** * 1: clockwise, -1: counterclockwise */ direction?: number; /** * Rounds per second */ speed?: number; /** * The z-index (defaults to 2000000000) */ zIndex?: number; /** * The CSS class to assign to the spinner */ className?: string; /** * Top position relative to parent (defaults to 50%) */ top?: string; /** * Left position relative to parent (defaults to 50%) */ left?: string; /** * Whether to render the default shadow (boolean). * A string can be used to set a custom box-shadow value. */ shadow?: boolean | string; /** * Element positioning */ position?: string; } ================================================ FILE: package.json ================================================ { "name": "spin.js", "version": "4.1.2", "description": "A spinning activity indicator", "files": [ "spin.css", "spin.d.ts", "spin.js", "SpinnerOptions.d.ts" ], "type": "module", "main": "spin.js", "types": "spin.d.ts", "author": "Felix Gnass ", "contributors": [ "Theodore Brown (https://github.com/theodorejb)", "Timothy Gu " ], "license": "MIT", "repository": { "type": "git", "url": "git://github.com/fgnass/spin.js.git" }, "keywords": [ "css", "progress indicator", "spin", "spinner" ], "scripts": { "prepare": "tsc && grunt --gruntfile Gruntfile.cjs && rollup -c", "gh-pages": "grunt gh-pages --gruntfile Gruntfile.cjs", "start": "serve site/" }, "devDependencies": { "grunt": "^1.6.1", "grunt-contrib-copy": "^1.0.0", "grunt-gh-pages": "^4.0.0", "rollup": "^4.18.1", "serve": "^14.2.3", "typescript": "^5.5.3" } } ================================================ FILE: rollup.config.js ================================================ export default [ { input: 'spin.js', output: { file: 'site/spin.umd.js', format: 'umd', name: 'Spin', }, }, { input: 'site/index.js', output: { file: 'site/bundle.js', format: 'iife' } } ]; ================================================ FILE: site/CNAME ================================================ spin.js.org ================================================ FILE: site/assets/gh-fork-ribbon.css ================================================ /*! * "Fork me on GitHub" CSS ribbon v0.2.3 | MIT License * https://github.com/simonwhitaker/github-fork-ribbon-css */ .github-fork-ribbon { width: 12.1em; height: 12.1em; position: absolute; overflow: hidden; top: 0; right: 0; z-index: 9999; pointer-events: none; font-size: 13px; text-decoration: none; text-indent: -999999px; } .github-fork-ribbon.fixed { position: fixed; } .github-fork-ribbon:hover, .github-fork-ribbon:active { background-color: rgba(0, 0, 0, 0.0); } .github-fork-ribbon:before, .github-fork-ribbon:after { /* The right and left classes determine the side we attach our banner to */ position: absolute; display: block; width: 15.38em; height: 1.54em; top: 3.23em; right: -3.23em; box-sizing: content-box; transform: rotate(45deg); } .github-fork-ribbon:before { content: ""; /* Add a bit of padding to give some substance outside the "stitching" */ padding: .38em 0; /* Set the base colour */ background-color: #39922c; /* Set a gradient: transparent black at the top to almost-transparent black at the bottom */ background-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.15)); /* Add a drop shadow */ box-shadow: 0 .15em .23em 0 rgba(0, 0, 0, 0.3); pointer-events: auto; } .github-fork-ribbon:after { /* Set the text from the data-ribbon attribute */ content: attr(data-ribbon); /* Set the text properties */ color: #fff; font: 700 1em "Helvetica Neue", Helvetica, Arial, sans-serif; line-height: 1.54em; text-decoration: none; text-shadow: 0 -.08em rgba(0, 0, 0, 0.5); text-align: center; text-indent: 0; /* Set the layout properties */ padding: .15em 0; margin: .15em 0; /* Add "stitching" effect */ border-width: .08em 0; border-style: dotted; border-color: #fff; border-color: rgba(255, 255, 255, 0.7); } .github-fork-ribbon.left-top, .github-fork-ribbon.left-bottom { right: auto; left: 0; } .github-fork-ribbon.left-bottom, .github-fork-ribbon.right-bottom { top: auto; bottom: 0; } .github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after { right: auto; left: -3.23em; } .github-fork-ribbon.left-bottom:before, .github-fork-ribbon.left-bottom:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after { top: auto; bottom: 3.23em; } .github-fork-ribbon.left-top:before, .github-fork-ribbon.left-top:after, .github-fork-ribbon.right-bottom:before, .github-fork-ribbon.right-bottom:after { transform: rotate(-45deg); } ================================================ FILE: site/assets/main.css ================================================ html, body { margin: 0; } html { background: url(bg.png); } body { color: #333; font-family: Helvetica, Arial, sans-serif; font-size: 16px; } ul { padding: 0 0 0 20px; } #content { max-width: 800px; line-height: 1.4em; padding: 0 40px 40px; text-shadow: 0px 1px 0px #fff; } #content img { max-width: 100%; } #footer { background: #202020; overflow: hidden; color: #888; font-size: 12px; padding: 20px 0 100px 50px; position: relative; } #footer:before { content: ''; display: block; box-shadow: 0 0 10px #000; height: 10px; position: absolute; left: 0; top: -10px; width: 100%; } #footer a.github { background: url(github.svg) no-repeat; background-size: 100%; width: 144px; height: 72px; text-indent: -999em; position: absolute; bottom: 30px; right: 30px; } a { color: #333; } a:hover { color: #000; } label { font-size: 14px; vertical-align: top; } #opts { background: #fff; padding: 10px; margin-top: 20px; box-sizing: border-box; } #opts label { display: inline-block; line-height: 25px; width: 80px; } h1 { font-size: 100px; } h1, h2, h3, #download a { font-family: 'Amaranth', sans-serif; } h2 { font-size: 28px; margin: 30px 0 20px; } h3 { margin: 30px 0 10px 0; } .mid_col { width: 20px; } #opts, #preview { width: 325px; border-radius: 10px; } #preview { position: relative; background: #333; height: 325px; } @media screen and (min-width: 545px) { h1 { font-size: 150px; } } @media screen and (min-width: 745px) { h1 { font-size: 175px; } #opts, .mid_col, #preview { display: table-cell; } #opts { width: auto; } #preview { height: 100%; } } @media screen and (min-width: 800px) { #preview { width: 375px; } } @media screen and (min-width: 900px) { h1 { font-size: 200px; } #content { margin-left: 100px; } } @media all and (-ms-high-contrast: none), (-ms-high-contrast: active) { /* styling fixes for IE 10 and IE 11 only */ #opts input[type="range"] { padding: 7px; width: 150px; } #opts, .mid_col, #preview { display: inline-block; } #preview { height: 400px; } } a.button { display: inline-block; background: #56ba4a; border: 3px solid #59a24f; color: #fff; border-radius: 5px; font-family: 'Amaranth', sans-serif; font-size: 24px; padding: 10px 30px; text-decoration: none; text-shadow: 1px 1px 1px #59a24f; box-shadow: 2px 2px 4px rgba(0,0,0,0.2); margin: 10px 20px 0 0; transition: all 0.25s ease; } a.button:hover { background: #2f8325; box-shadow: none; } .authors { display: grid; grid-template-columns: repeat(auto-fill, minmax(275px, 1fr)); grid-gap: 50px; } .contact { background-color: #e1e1e1; padding: 24px; text-align: center; box-shadow: 2px 2px 10px #bbb; } .contact h3 { margin: 20px 0; } .contact p { margin-bottom: 0; } .contact img { border-radius: 50%; } ================================================ FILE: site/assets/prism.css ================================================ /* PrismJS 1.20.0 https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript */ /** * okaidia theme for JavaScript, CSS and HTML * Loosely based on Monokai textmate theme by http://www.monokai.nl/ * @author ocodia */ code[class*="language-"], pre[class*="language-"] { color: #f8f8f2; background: none; text-shadow: 0 1px rgba(0, 0, 0, 0.3); font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; font-size: 1em; text-align: left; white-space: pre; word-spacing: normal; word-break: normal; word-wrap: normal; line-height: 1.5; -moz-tab-size: 4; -o-tab-size: 4; tab-size: 4; -webkit-hyphens: none; -moz-hyphens: none; -ms-hyphens: none; hyphens: none; } /* Code blocks */ pre[class*="language-"] { padding: 1em; margin: .5em 0; overflow: auto; border-radius: 0.3em; } :not(pre) > code[class*="language-"], pre[class*="language-"] { background: #272822; } /* Inline code */ :not(pre) > code[class*="language-"] { padding: .1em; border-radius: .3em; white-space: normal; } .token.comment, .token.prolog, .token.doctype, .token.cdata { color: #8292a2; } .token.punctuation { color: #f8f8f2; } .token.namespace { opacity: .7; } .token.property, .token.tag, .token.constant, .token.symbol, .token.deleted { color: #f92672; } .token.boolean, .token.number { color: #ae81ff; } .token.selector, .token.attr-name, .token.string, .token.char, .token.builtin, .token.inserted { color: #a6e22e; } .token.operator, .token.entity, .token.url, .language-css .token.string, .style .token.string, .token.variable { color: #f8f8f2; } .token.atrule, .token.attr-value, .token.function, .token.class-name { color: #e6db74; } .token.keyword { color: #66d9ef; } .token.regex, .token.important { color: #fd971f; } .token.important, .token.bold { font-weight: bold; } .token.italic { font-style: italic; } .token.entity { cursor: help; } ================================================ FILE: site/assets/prism.js ================================================ /* PrismJS 1.20.0 https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript */ var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=l.reach);k+=y.value.length,y=y.next){var b=y.value;if(t.length>n.length)return;if(!(b instanceof W)){var x=1;if(h&&y!=t.tail.prev){m.lastIndex=k;var w=m.exec(n);if(!w)break;var A=w.index+(f&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof W)continue;for(var E=y;E!==t.tail&&(Sl.reach&&(l.reach=j);var C=y.prev;L&&(C=I(t,C,L),k+=L.length),z(t,C,x);var _=new W(o,g?M.tokenize(O,g):O,v,O);y=I(t,C,_),N&&I(t,y,N),1"+a.content+""},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var e=M.util.currentScript();function t(){M.manual||M.highlightAll()}if(e&&(M.filename=e.src,e.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var r=document.readyState;"loading"===r||"interactive"===r&&e&&e.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)}return M}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism); Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^]*?>)(?:))*\\]\\]>|(?!)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml; !function(e){var s=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+s.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+s.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+s.source+")*?(?=\\s*\\{)"),string:{pattern:s,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var t=e.languages.markup;t&&(t.tag.addInlined("style","css"),e.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/\s*style=("|')(?:\\[\s\S]|(?!\1)[^\\])*\1/i,inside:{"attr-name":{pattern:/^\s*style/i,inside:t.tag.inside},punctuation:/^\s*=\s*['"]|['"]\s*$/,"attr-value":{pattern:/.+/i,inside:e.languages.css}},alias:"language-css"}},t.tag))}(Prism); Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/}; Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript; ================================================ FILE: site/example/positioning.css ================================================ body { font-family: Helvetica, Arial, sans-serif; font-size: 16px; padding: 0; margin: 0; } div { position: relative; border-radius: 10px; } #target1 { background: #aaa url(../assets/crosshair.gif) center center no-repeat; width: 99px; height: 99px; padding: 15px; } #target2 { background: #bbb url(../assets/crosshair.gif) center center no-repeat; width: 99px; height: 99px; padding: 15px; } #target3 { background: #ccc url(../assets/crosshair.gif) center center no-repeat; width: 99px; height: 99px; padding: 15px; } #target4 { background: #ddd; padding: 15px; width: 375px; height: 66px; padding: 66px 38px; } @keyframes spinner-line-fade-custom { 0%, 59%, 100% { opacity: 0.25; /* minimum opacity */ } 60% { opacity: 1; } } ================================================ FILE: site/example/positioning.html ================================================ spin.js
================================================ FILE: site/example/positioning.js ================================================ import { Spinner } from "../spin.js"; new Spinner({ radius: 10, length: 40, color: ['red', 'green', 'blue'] }) .spin(document.getElementById('target1')); new Spinner({ radius: 40, length: 10 }).spin(document.getElementById('target2')); new Spinner({ top: 0, left: 0 }).spin(document.getElementById('target3')); new Spinner({ radius: 32, length: 0, width: 10, color: '#c13d3d', animation: 'spinner-line-fade-custom' }) .spin(document.getElementById('target4')); ================================================ FILE: site/index.html ================================================ spin.js Fork me on GitHub

spin.js

Example
















Features

  • No images
  • No dependencies
  • Highly configurable
  • Resolution independent
  • Uses CSS keyframe animations
  • Works in all major browsers
  • Includes TypeScript definitions
  • Distributed as a native ECMAScript module
  • MIT License

Spin.js dynamically creates spinning activity indicators that can be used as a resolution-independent replacement for loading GIFs.

Installation

Run npm install spin.js, or save the spin.js and spin.css files in your repository.

JS bundling

For best performance and compatibility, it is recommended to use a module bundler such as Parcel, Rollup, or Webpack to create a production-ready code bundle. However, in modern browsers it is also possible to directly load the module via a script tag:

<script type="module" src="node_modules/spin.js/spin.js"></script>

If for some reason you can't use ECMAScript modules or a module bundler, download spin.umd.js and save it in your repository. The UMD script can be used as follows:

var spinner = new Spin.Spinner(opts).spin(target);

Note that the UMD version is only available as a temporary workaround. Longer term it is recommended to migrate to the standard ECMAScript module.

CSS

Load the spin.css file to include the default animation presets. You could alternatively create a custom CSS keyframe animation (in your own CSS file) and set it using the animation property.

Note: do not use <script> or <link> tags directly referencing the files on this website. Doing this is likely to break your app whenever a major new version is released.

Usage

Run the following code when the document has loaded:


The spin() method creates the necessary HTML elements and starts the animation. If a target element is passed as argument, the spinner is added as first child and horizontally and vertically centered.

Manual insertion

In order to manually insert the spinner into the DOM you can invoke the spin() method without any arguments and use the el property to access the HTML element:

var spinner = new Spinner().spin();
target.appendChild(spinner.el);

Hiding the spinner

To hide the spinner, invoke the stop() method, which removes the UI elements from the DOM and stops the animation. Stopped spinners may be reused by calling spin() again.

Positioning

The spinner is absolutely positioned at 50% of its offset parent. You may specify a top and left option to position the spinner manually.

Note: The spinner element is a 0×0 pixel div that represents the center of the spinner. Hence, if you passed {top:0, left:0} only the lower right quarter of the spinner would be inside the target's bounding box.

The spinner element must be surrounded by an element using relative positioning, or the spinner will be outside of the parent element.

Supported browsers

Spin.js has been successfully tested in the following browsers:

  • Firefox
  • Microsoft Edge
  • Safari
  • Chrome
  • Internet Explorer 10+

Changes

See CHANGELOG.md

Support

If you encounter any problems, please use the GitHub issue tracker.

Authors

Felix Gnass

JS 💛 React ⚛️ NodeJS 💚

Twitter: @fgnass

Theodore Brown

PHP 🐘 TypeScript 📐 SQL 📊

Website: theodorejb.me
Twitter: @theodorejb

================================================ FILE: site/index.js ================================================ import { Spinner } from "../spin.js"; var inputs = document.querySelectorAll('#opts input[type="range"], #opts input[type="color"], #opts input[type="text"], #opts select'); var cbInputs = document.querySelectorAll('#opts input[type="checkbox"]'); var spinnerEl = document.getElementById('preview'); var shareEl = document.getElementById('share'); var spinner; var params = {}; var hash = /^#\?(.*)/.exec(location.hash); if (hash) { shareEl.checked = true; hash[1].split(/&/).forEach(function (pair) { var kv = pair.split(/=/); params[kv[0]] = decodeURIComponent(kv[kv.length - 1]); }); } for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; var val = params[input.name]; if (val !== undefined) { input.value = val; } if (input.tagName === 'SELECT' || navigator.userAgent.indexOf('Trident') !== -1) { // "input" event doesn't work on range inputs in Internet Explorer var event = 'change'; } else { event = 'input'; } input.addEventListener(event, update); } for (var i = 0; i < cbInputs.length; i++) { var input = cbInputs[i]; input.checked = !!params[input.name]; input.addEventListener('click', update); } shareEl.addEventListener('click', function () { var value = '#!'; if (shareEl.checked) { var opts = getOptionsFromInputs(); value = '#?' + getParamStringFromOpts(opts); } window.location.replace(value); }); update(); function update() { var opts = getOptionsFromInputs(); if (spinner) { spinner.stop(); } spinner = new Spinner(opts).spin(spinnerEl); if (shareEl.checked) { window.location.replace('#?' + getParamStringFromOpts(opts)); } let codeEl = document.getElementById('spinner-options'); codeEl.textContent = getOptionsCode(opts); Prism.highlightElement(codeEl); } function getOptionsCode(options) { var optDescriptions = { lines: 'The number of lines to draw', length: 'The length of each line', width: 'The line thickness', radius: 'The radius of the inner circle', scale: 'Scales overall size of the spinner', corners: 'Corner roundness (0..1)', color: 'CSS color or array of colors', fadeColor: 'CSS color or array of colors', speed: 'Rounds per second', rotate: 'The rotation offset', animation: 'The CSS animation name for the lines', direction: '1: clockwise, -1: counterclockwise', zIndex: 'The z-index (defaults to 2e9)', className: 'The CSS class to assign to the spinner', top: 'Top position relative to parent', left: 'Left position relative to parent', shadow: 'Box-shadow for the lines', position: 'Element positioning', }; let code = "import {Spinner} from 'spin.js';\n\n"; code += "var opts = {\n"; for (let opt in options) { let value = options[opt]; if (typeof value === 'string') { value = "'" + value + "'"; } code += " " + opt + ": " + value + ", // " + optDescriptions[opt] + "\n"; } code += "};\n\n"; code += "var target = document.getElementById('foo');\n"; code += "var spinner = new Spinner(opts).spin(target);"; return code; } function getOptionsFromInputs() { var opts = {}; for (var i = 0; i < inputs.length; i++) { var input = inputs[i]; var val = input.value; if (input.classList.contains('percent')) { val += '%'; } else if (!input.classList.contains('string')) { val = parseFloat(val); } opts[input.name] = val; } // set all options so they can be shown in code example opts['zIndex'] = 2e9; opts['className'] = 'spinner'; opts['position'] = 'absolute'; for (var i = 0; i < cbInputs.length; i++) { var input = cbInputs[i]; opts[input.name] = input.checked; document.getElementById('opt-' + input.name).textContent = input.checked; } return opts; } function getParamStringFromOpts(opts) { var params = []; for (var prop in opts) { var val = opts[prop]; if (val !== false) { if (typeof val === 'string' && val.slice(-1) === '%') { val = val.slice(0, -1); } params.push(prop + '=' + encodeURIComponent(val)); } } return params.join('&'); } ================================================ FILE: site/serve.json ================================================ { "headers": [ { "source" : "index.html", "headers" : [{ "key" : "Content-Security-Policy", "value" : "default-src 'self'; style-src 'self' fonts.googleapis.com; font-src 'self' fonts.gstatic.com; img-src 'self' www.gravatar.com;" }] } ] } ================================================ FILE: spin.css ================================================ @keyframes spinner-line-fade-more { 0%, 100% { opacity: 0; /* minimum opacity */ } 1% { opacity: 1; } } @keyframes spinner-line-fade-quick { 0%, 39%, 100% { opacity: 0.25; /* minimum opacity */ } 40% { opacity: 1; } } @keyframes spinner-line-fade-default { 0%, 100% { opacity: 0.22; /* minimum opacity */ } 1% { opacity: 1; } } @keyframes spinner-line-shrink { 0%, 25%, 100% { /* minimum scale and opacity */ transform: scale(0.5); opacity: 0.25; } 26% { transform: scale(1); opacity: 1; } } ================================================ FILE: spin.ts ================================================ import { SpinnerOptions } from './SpinnerOptions.js'; export { SpinnerOptions } from './SpinnerOptions.js'; const defaults: Required = { lines: 12, length: 7, width: 5, radius: 10, scale: 1.0, corners: 1, color: '#000', fadeColor: 'transparent', animation: 'spinner-line-fade-default', rotate: 0, direction: 1, speed: 1, zIndex: 2e9, className: 'spinner', top: '50%', left: '50%', shadow: '0 0 1px transparent', // prevent aliased lines position: 'absolute', }; export class Spinner { private opts: Required; /** * The Spinner's HTML element - can be used to manually insert the spinner into the DOM */ public el: HTMLElement | undefined; constructor(opts: SpinnerOptions = {}) { this.opts = { ...defaults, ...opts }; } /** * Adds the spinner to the given target element. If this instance is already * spinning, it is automatically removed from its previous target by calling * stop() internally. */ spin(target?: HTMLElement): this { this.stop(); this.el = document.createElement('div'); this.el.className = this.opts.className; this.el.setAttribute('role', 'progressbar'); this.el.style.position = this.opts.position; this.el.style.width = "0"; this.el.style.zIndex = this.opts.zIndex.toString(); this.el.style.left = this.opts.left; this.el.style.top = this.opts.top; this.el.style.transform = `scale(${this.opts.scale})`; if (target) { target.insertBefore(this.el, target.firstChild || null); } drawLines(this.el, this.opts); return this; } /** * Stops and removes the Spinner. * Stopped spinners may be reused by calling spin() again. */ stop(): this { if (this.el) { if (this.el.parentNode) { this.el.parentNode.removeChild(this.el); } this.el = undefined; } return this; } } /** * Returns the line color from the given string or array. */ function getColor(color: string | string[], idx: number): string { return typeof color == 'string' ? color : color[idx % color.length]; } /** * Internal method that draws the individual lines. */ function drawLines(el: HTMLElement, opts: Required): void { let borderRadius = (Math.round(opts.corners * opts.width * 500) / 1000) + 'px'; let shadow = 'none'; if (opts.shadow === true) { shadow = '0 2px 4px #000'; // default shadow } else if (typeof opts.shadow === 'string') { shadow = opts.shadow; } let shadows = parseBoxShadow(shadow); for (let i = 0; i < opts.lines; i++) { let degrees = ~~(360 / opts.lines * i + opts.rotate); let backgroundLine = document.createElement('div'); backgroundLine.style.position = 'absolute'; backgroundLine.style.top = `${-opts.width / 2}px`; backgroundLine.style.width = (opts.length + opts.width) + 'px'; backgroundLine.style.height = opts.width + 'px'; backgroundLine.style.background = getColor(opts.fadeColor, i); backgroundLine.style.borderRadius = borderRadius; backgroundLine.style.transformOrigin = 'left'; backgroundLine.style.transform = `rotate(${degrees}deg) translateX(${opts.radius}px)`; let delay = i * opts.direction / opts.lines / opts.speed; delay -= 1 / opts.speed; // so initial animation state will include trail let line = document.createElement('div'); line.style.width = '100%'; line.style.height = '100%'; line.style.background = getColor(opts.color, i); line.style.borderRadius = borderRadius; line.style.boxShadow = normalizeShadow(shadows, degrees); line.style.animation = `${1 / opts.speed}s linear ${delay}s infinite ${opts.animation}`; backgroundLine.appendChild(line); el.appendChild(backgroundLine); } } interface ParsedShadow { prefix: string, x: number, y: number, xUnits: string, yUnits: string, end: string, } function parseBoxShadow(boxShadow: string): ParsedShadow[] { let regex = /^\s*([a-zA-Z]+\s+)?(-?\d+(\.\d+)?)([a-zA-Z]*)\s+(-?\d+(\.\d+)?)([a-zA-Z]*)(.*)$/; let shadows: ParsedShadow[] = []; for (let shadow of boxShadow.split(',')) { let matches = shadow.match(regex); if (matches === null) { continue; // invalid syntax } let x = +matches[2]; let y = +matches[5]; let xUnits = matches[4]; let yUnits = matches[7]; if (x === 0 && !xUnits) { xUnits = yUnits; } if (y === 0 && !yUnits) { yUnits = xUnits; } if (xUnits !== yUnits) { continue; // units must match to use as coordinates } shadows.push({ prefix: matches[1] || '', // could have value of 'inset' or undefined x: x, y: y, xUnits: xUnits, yUnits: yUnits, end: matches[8], }); } return shadows; } /** * Modify box-shadow x/y offsets to counteract rotation */ function normalizeShadow(shadows: ParsedShadow[], degrees: number): string { let normalized: string[] = []; for (let shadow of shadows) { let xy = convertOffset(shadow.x, shadow.y, degrees); normalized.push(shadow.prefix + xy[0] + shadow.xUnits + ' ' + xy[1] + shadow.yUnits + shadow.end); } return normalized.join(', '); } function convertOffset(x: number, y: number, degrees: number) { let radians = degrees * Math.PI / 180; let sin = Math.sin(radians); let cos = Math.cos(radians); return [ Math.round((x * cos + y * sin) * 1000) / 1000, Math.round((-x * sin + y * cos) * 1000) / 1000, ]; } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "module": "NodeNext", "moduleResolution": "NodeNext", "target": "ES5", "forceConsistentCasingInFileNames": true, "noUnusedLocals": true, "strict": true, "declaration": true }, "files": [ "spin.ts" ] }