{"id":15789832,"url":"https://github.com/small-tech/spinners","last_synced_at":"2025-03-14T13:30:32.501Z","repository":{"id":57160992,"uuid":"381806414","full_name":"small-tech/spinners","owner":"small-tech","description":"Two indeterminate progress spinners, one with lines and the other with dots (available in animated SVG, animated PNG, JavaScript, and Svelte).","archived":false,"fork":false,"pushed_at":"2021-07-06T10:23:58.000Z","size":881,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-05T07:43:56.130Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/small-tech.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-06-30T19:03:57.000Z","updated_at":"2021-11-17T11:45:09.000Z","dependencies_parsed_at":"2022-09-09T10:23:17.938Z","dependency_job_id":null,"html_url":"https://github.com/small-tech/spinners","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/small-tech%2Fspinners","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/small-tech%2Fspinners/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/small-tech%2Fspinners/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/small-tech%2Fspinners/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/small-tech","download_url":"https://codeload.github.com/small-tech/spinners/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243584022,"owners_count":20314680,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-04T22:03:53.724Z","updated_at":"2025-03-14T13:30:32.085Z","avatar_url":"https://github.com/small-tech.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Spinners\n\nThis package has two indeterminate progress spinners, one with lines:\n\n\u003c!--\n  Embed SVG with animated PNG fallback because GitHub can\n  do AI pair programming but not animated SVGs, apparently.\n--\u003e\n\u003csvg height=\"48\" stroke=\"currentColor\" viewBox=\"0 0 64 64\"\u003e\u003cg stroke-width=\"6\" stroke-linecap=\"round\"\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(180)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"1;0.85;0.7;0.65;0.55;0.45;0.35;0.25;0.15;0.1;0;1\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(210)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0;1;0.85;0.7;0.65;0.55;0.45;0.35;0.25;0.15;0.1;0\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(240)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.1;0;1;0.85;0.7;0.65;0.55;0.45;0.35;0.25;0.15;0.1\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(270)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.15;0.1;0;1;0.85;0.7;0.65;0.55;0.45;0.35;0.25;0.15\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(300)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.25;0.15;0.1;0;1;0.85;0.7;0.65;0.55;0.45;0.35;0.25\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(330)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.35;0.25;0.15;0.1;0;1;0.85;0.7;0.65;0.55;0.45;0.35\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(0)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.45;0.35;0.25;0.15;0.1;0;1;0.85;0.7;0.65;0.55;0.45\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(30)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.55;0.45;0.35;0.25;0.15;0.1;0;1;0.85;0.7;0.65;0.55\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(60)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.65;0.55;0.45;0.35;0.25;0.15;0.1;0;1;0.85;0.7;0.65\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(90)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.7;0.65;0.55;0.45;0.35;0.25;0.15;0.1;0;1;0.85;0.7\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(120)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0.85;0.7;0.65;0.55;0.45;0.35;0.25;0.15;0.1;0;1;0.85\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"18\" y2=\"29\" transform=\"translate(32,32) rotate(150)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"1;0.85;0.7;0.65;0.55;0.45;0.35;0.25;0.15;0.1;0;1\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003c/g\u003e\u003cforeignObject\u003e\u003cimg src=\"./lines.png\" width=\"48px\"\u003e\u003c/foreignObject\u003e\u003c/svg\u003e\n\n…and the other with dots:\n\n\u003csvg fill=\"currentColor\" height=\"64\" viewBox=\"0 0 64 64\"\u003e\u003cg\u003e\u003ccircle cx=\"16\" cy=\"32\" stroke-width=\"0\"\u003e\u003canimate attributeName=\"fill-opacity\" dur=\"750ms\" values=\".5;.6;.8;1;.8;.6;.5;.5\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003canimate attributeName=\"r\" dur=\"750ms\" values=\"3;3;4;5;6;5;4;3\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/circle\u003e\u003ccircle cx=\"32\" cy=\"32\" stroke-width=\"0\"\u003e\u003canimate attributeName=\"fill-opacity\" dur=\"750ms\" values=\".5;.5;.6;.8;1;.8;.6;.5\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003canimate attributeName=\"r\" dur=\"750ms\" values=\"4;3;3;4;5;6;5;4\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/circle\u003e\u003ccircle cx=\"48\" cy=\"32\" stroke-width=\"0\"\u003e\u003canimate attributeName=\"fill-opacity\" dur=\"750ms\" values=\".6;.5;.5;.6;.8;1;.8;.6\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003canimate attributeName=\"r\" dur=\"750ms\" values=\"5;4;3;3;4;5;6;5\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/circle\u003e\u003c/g\u003e\u003cforeignObject\u003e\u003cimg src=\"./dots.png\" width=\"64px\"\u003e\u003c/foreignObject\u003e\u003c/svg\u003e\n\nThey are [two of the spinners](https://developer.mozilla.org/en-US/docs/Web/SVG/Element/animate) from [Ionic Framework](https://ionicframework.com/docs/api/spinner).\n\n## Why?\n\nBecause sometimes you just need a simple spinner without a 5-tonne framework attached.\n\n## Usage\n\n### Note on accessibility\n\nWhen including a spinner in your site or app, the element that contains your spinner should be set as an [ARIA live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) and provide meaningful, app-specific feedback on its current state.\n\n__Learn more:__ [“It’s alive!”: Apps That Feed Back Accessibly](https://www.smashingmagazine.com/2015/04/its-alive-apps-that-feed-back-accessibly/), [More Accessible Skeletons](https://adrianroselli.com/2020/11/more-accessible-skeletons.html)\n\n\n### Just the SVGs\n\nIf you just want the SVGs, you can grab them below (right click and save):\n\n  - [Lines](https://raw.githubusercontent.com/small-tech/spinners/main/lines.svg)\n  - [Dots](https://raw.githubusercontent.com/small-tech/spinners/main/dots.svg)\n\n\u003cdetails\u003e\n  \u003csummary\u003e\u003cstrong\u003eSVG code (copy and paste)\u003c/strong\u003e\u003c/summary\u003e\n\n  #### Lines:\n\n  ```svg\n  \u003csvg stroke=\"currentColor\" height=\"1em\" viewBox=\"0 0 64 64\"\u003e\u003cg stroke-width=\"4\" stroke-linecap=\"round\"\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(180)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"1;.85;.7;.65;.55;.45;.35;.25;.15;.1;0;1\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(210)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"0;1;.85;.7;.65;.55;.45;.35;.25;.15;.1;0\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(240)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".1;0;1;.85;.7;.65;.55;.45;.35;.25;.15;.1\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(270)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".15;.1;0;1;.85;.7;.65;.55;.45;.35;.25;.15\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(300)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".25;.15;.1;0;1;.85;.7;.65;.55;.45;.35;.25\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(330)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".35;.25;.15;.1;0;1;.85;.7;.65;.55;.45;.35\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(0)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".45;.35;.25;.15;.1;0;1;.85;.7;.65;.55;.45\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(30)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".55;.45;.35;.25;.15;.1;0;1;.85;.7;.65;.55\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(60)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".65;.55;.45;.35;.25;.15;.1;0;1;.85;.7;.65\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(90)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".7;.65;.55;.45;.35;.25;.15;.1;0;1;.85;.7\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(120)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\".85;.7;.65;.55;.45;.35;.25;.15;.1;0;1;.85\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003cline y1=\"12\" y2=\"20\" transform=\"translate(32,32) rotate(150)\"\u003e\u003canimate attributeName=\"stroke-opacity\" dur=\"750ms\" values=\"1;.85;.7;.65;.55;.45;.35;.25;.15;.1;0;1\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/line\u003e\u003c/g\u003e\u003c/svg\u003e\n  ```\n\n  #### Dots:\n\n  ```svg\n  \u003csvg height=\"1em\" fill=\"currentColor\" viewBox=\"0 0 64 64\"\u003e\u003cg\u003e\u003ccircle cx=\"16\" cy=\"32\" stroke-width=\"0\"\u003e\u003canimate attributeName=\"fill-opacity\" dur=\"750ms\" values=\".5;.6;.8;1;.8;.6;.5;.5\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003canimate attributeName=\"r\" dur=\"750ms\" values=\"3;3;4;5;6;5;4;3\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/circle\u003e\u003ccircle cx=\"32\" cy=\"32\" stroke-width=\"0\"\u003e\u003canimate attributeName=\"fill-opacity\" dur=\"750ms\" values=\".5;.5;.6;.8;1;.8;.6;.5\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003canimate attributeName=\"r\" dur=\"750ms\" values=\"4;3;3;4;5;6;5;4\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/circle\u003e\u003ccircle cx=\"48\" cy=\"32\" stroke-width=\"0\"\u003e\u003canimate attributeName=\"fill-opacity\" dur=\"750ms\" values=\".6;.5;.5;.6;.8;1;.8;.6\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003canimate attributeName=\"r\" dur=\"750ms\" values=\"5;4;3;3;4;5;6;5\" repeatCount=\"indefinite\"\u003e\u003c/animate\u003e\u003c/circle\u003e\u003c/g\u003e\u003c/svg\u003e\n  ```\n\n\u003c/details\u003e\n\n### Plain old JavaScript\n\nYou can also use the spinners via plain old JavaScript:\n\n1. `require()` or `import()` the spinner you want in your project.\n\n2. Instantiate the class and pass it an options object that contains the DOM element that you want the class to bind to any properties you want to include. Valid properties are:\n\n    - `size`: the size of the spinner (height; width is auto-calculated). Default: `1em`. You can use any CSS sizing unit.\n    - `colour`: the colour of the spinner (default: uses the colour of the surrounding context). You can use any valid CSS colour value.\n    - `show`: a boolean indicating whether the spinner is visible. Default: `true`.\n\n#### Example (JavaScript)\n\n```html\n\u003cmain\u003e\n  \u003cdiv id='spinner'\u003e\u003c/div\u003e\n\n  \u003cscript type='module'\u003e\n    import { Lines } from 'https://unpkg.com/@small-tech/spinners@1.0.0/dist/index.mjs'\n\n    const lines = new Lines({\n      target: document.getElementById('spinner'),\n      props: {\n        size: '2em',\n        colour: 'SlateGrey'\n      }\n    })\n  \u003c/script\u003e\n\u003c/main\u003e\n```\nYou can find a version of this example in the [examples/javascript](examples/javascript) folder.\n\n### Svelte\n\nFinally, you can also use the spinners in your [Svelte](https://dev.svelte) projects.\n\nThe example below is functionally identical to the plain old JavaScript one, above.\n\n```svelte\n\u003cscript\u003e\n  import { Lines } from '@small-tech/spinners'\n\u003c/script\u003e\n\n\u003cLines size='2em' colour='SlateGrey' /\u003e\n```\n\nYou can find a version of this example in the [examples/svelte](examples/svelte) folder.\n\n### Generate PNGs\n\nIn an ideal world, animated SVGs (yes, even those that use [SMIL animations](https://developer.mozilla.org/en-US/docs/Web/SVG/SVG_animation_with_SMIL) – which are great, by the way) should be supported everywhere. Sadly, we don’t live in an ideal world. We live in a world where GitHub can \u003cstrike\u003emass-violate the copyright of free and open source projects\u003c/strike\u003e implement “AI pair programming” but cannot render a simple animated SVG properly.\n\nSo, if you’re viewing this readme on GitHub, what you’re seeing above are bitmap versions (animated PNGs) of the spinners exported [using tiny scripts](/png) I wrote for that purpose. The reason they’re a shade of blue is because, unlike the SVGs which default to using the `currentColor` of their context, I had to export [a colour that would work with acceptable contrast on both light and dark mode](https://web.archive.org/web/20160214165231/trace.wisc.edu/contrast-ratio-examples/PassingMidColorSamples_4-5to1.htm).\n\nThey’re embedded as fallbacks within `\u003cforeignObject\u003e` tags inside of the inline SVGs in the readme (so, for example, if you’re viewing them in [VSCodium](https://vscodium.com/), you will only see the SVG spinners, not two sets of spinners).\n\nYou can also use these scripts to generate your own animated PNG versions of the spinners, should you so desire.\n\nFirst, clone this repository. Then, install the dependencies:\n\n```shell\ncd png\nnpm install\ncd ..\n```\n\nAnd run the scripts:\n\n```shell\nnode png/lines\nnode png/dots\n```\n\nYou can customise the size, colour, and frame multiplier (animation resolution) via options.\n\nTo see all options:\n\n```shell\nnode png/lines --help\nnode png/dots --help\n```\n\nIf you’re interested in the nitty gritties of image manipulation, view the source of the [lines.js](png/lines.js) and [dots.js](png/dots.js) scripts to see an example of how to convert SVGs to animated PNGs in Node.js using the [svg-png-converter]() and [node-apng]() modules.\n\n_(Yes, I know I should refactor the redundancies between those two scripts. No, I don’t have the time to do that now. And yes, I’ve already spent far more time than on this than any reasonable person should have.)_\n\n## Like this? Fund us!\n\n[Small Technology Foundation](https://small-tech.org) is a tiny, independent not-for-profit.\n\nWe exist in part thanks to patronage by people like you. If you share [our vision](https://small-tech.org/about/#small-technology) and want to support our work, please [become a patron or donate to us](https://small-tech.org/fund-us) today and help us continue to exist.\n\n## License\n\n[ISC](./license) ([Ionic Framework’s spinners are under MIT](./license))\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmall-tech%2Fspinners","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmall-tech%2Fspinners","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmall-tech%2Fspinners/lists"}