{"id":15486092,"url":"https://github.com/smastrom/vue-global-loader","last_synced_at":"2025-04-15T22:50:10.101Z","repository":{"id":211739516,"uuid":"713572954","full_name":"smastrom/vue-global-loader","owner":"smastrom","description":"🌀 Global loaders made easy for Vue and Nuxt.","archived":false,"fork":false,"pushed_at":"2024-05-03T19:00:06.000Z","size":153,"stargazers_count":16,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-04-15T22:49:59.115Z","etag":null,"topics":["loader","loading","loading-indicator","loading-screen","loading-spinner","nuxt","nuxtjs","spinner","spinners","vue","vuejs"],"latest_commit_sha":null,"homepage":"https://vue-global-loader.pages.dev","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/smastrom.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-11-02T19:49:07.000Z","updated_at":"2025-02-05T16:45:45.000Z","dependencies_parsed_at":"2023-12-19T01:01:54.962Z","dependency_job_id":"78312bc2-b113-4001-8bcf-c57d7e634ec1","html_url":"https://github.com/smastrom/vue-global-loader","commit_stats":{"total_commits":57,"total_committers":1,"mean_commits":57.0,"dds":0.0,"last_synced_commit":"67d7bdb7577b9282996646ef1e7bf432c6da8e10"},"previous_names":["smastrom/vue-global-loader"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smastrom%2Fvue-global-loader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smastrom%2Fvue-global-loader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smastrom%2Fvue-global-loader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/smastrom%2Fvue-global-loader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/smastrom","download_url":"https://codeload.github.com/smastrom/vue-global-loader/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249167434,"owners_count":21223505,"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":["loader","loading","loading-indicator","loading-screen","loading-spinner","nuxt","nuxtjs","spinner","spinners","vue","vuejs"],"created_at":"2024-10-02T06:06:22.278Z","updated_at":"2025-04-15T22:50:10.086Z","avatar_url":"https://github.com/smastrom.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Vue Global Loader\n\n![npm](https://img.shields.io/npm/v/vue-global-loader?color=46c119) ![dependencies](https://img.shields.io/badge/dependencies-0-success) ![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/smastrom/vue-global-loader/tests.yml?branch=main\u0026label=tests)\n\n### Global loaders made easy for Vue and Nuxt.\n\n[Live Demo](https://vue-global-loader.pages.dev/) — [Vite Example](https://stackblitz.com/edit/vitejs-vite-umqonc?file=src%2FApp.vue) — [Nuxt Example](https://stackblitz.com/edit/nuxt-starter-rpnobz?file=app.vue)\n\n\u003cbr /\u003e\n\n\u003e :bulb: Please note that this package only works with [Vite](https://vitejs.dev/) and [Nuxt](https://nuxt.com/) setups. Usage without a build-step is not supported.\n\n\u003cbr /\u003e\n\n## Intro\n\nI find it useful to display global loaders in single-page apps. For example:\n\n- When redirecting to an external payment page\n- When navigating to an internal page after a critical operation such as sign-in/sign-out\n- When navigating to an internal page with plenty of data\n\nSince I was re-creating the same logic and markup over and over again, I decided to publish a package for it.\n\n## Features\n\nThis package simplifies the usage of a single, top-level global loader by:\n\n- Installing a global store that sits above your routes, so you can control it between pages\n- Providing a practical API customizable via few key props (get started in 10 seconds)\n- Properly disable user interactions with the rest of the app while the loader is displayed\n- Announcing a screen reader message when the loader is displayed\n- Dynamically update options from anywhere in the app and apply options only to a specific loader\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Usage](#usage)\n- [Customization](#customization)\n  - [Spinners](#spinners)\n  - [Options](#options)\n    - [Default Options](#default-options)\n    - [Scoped Options](#scoped-options)\n    - [Updating default options](#updating-default-options)\n  - [Callbacks / Lifecycle](#callbacks--lifecycle)\n- [API](#api)\n- [Further Notes](#further-notes)\n  - [When to use it](#when-to-use-it)\n  - [When to not use it](#when-to-not-use-it)\n- [SPA Loading Templates](#spa-loading-templates)\n\n## Installation\n\n```bash\npnpm add vue-global-loader\n```\n\n```bash\nyarn add vue-global-loader\n```\n\n```bash\nnpm i vue-global-loader\n```\n\n### Vite\n\n\u003e :bulb: See [↓ below](#nuxt) for **Nuxt**\n\n**main.js**\n\n```js\nimport { createApp } from 'vue'\nimport { globalLoader } from 'vue-global-loader'\n\nimport App from './App.vue'\n\nconst app = createApp(App)\n\napp.use(globalLoader, {\n  // Options\n})\n\napp.mount('#app')\n```\n\n**App.vue**\n\n```vue\n\u003cscript setup\u003e\nimport GlobalLoader from 'vue-global-loader/GlobalLoader.vue'\nimport CircleSpinner from 'vue-global-loader/CircleSpinner.vue'\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cGlobalLoader\u003e\n    \u003cCircleSpinner /\u003e\n  \u003c/GlobalLoader\u003e\n\n  \u003c!-- RouterView --\u003e\n\u003c/template\u003e\n```\n\n### Nuxt\n\n**nuxt.config.ts**\n\n```ts\nexport default defineNuxtConfig({\n  modules: ['vue-global-loader/nuxt'],\n  globalLoader: {\n    // Options\n  }\n})\n```\n\n**app.vue**\n\n```vue\n\u003ctemplate\u003e\n  \u003cGlobalLoader\u003e\n    \u003cCircleSpinner /\u003e\n  \u003c/GlobalLoader\u003e\n\n  \u003c!-- NuxtLayout, NuxtPage... --\u003e\n\u003c/template\u003e\n```\n\n### Usage\n\n`pages/login.vue`\n\n\u003e :bulb: No need to state the imports if using **Nuxt** (everything is auto-imported)\n\n```vue\n\u003cscript setup\u003e\nimport { useRouter } from 'vue-router'\nimport { useGlobalLoader } from 'vue-global-loader'\n\nconst { displayLoader, destroyLoader, isLoading } = useGlobalLoader({\n  screenReaderMessage:\n    'Signing-in, redirecting to the dashboard, please wait...'\n})\n\nconst router = useRouter()\n\nasync function signIn() {\n  try {\n    displayLoader() // Display loader...\n    await auth.signIn()\n    router.push('/dashboard')\n  } catch (err) {\n    console.error(err)\n    destroyLoader()\n  }\n}\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cbutton :disabled=\"isLoading\" @click=\"signIn\"\u003eSign-in\u003c/button\u003e\n\u003c/template\u003e\n```\n\n`pages/dashboard.vue`\n\n\u003e :bulb: No need to state the imports if using **Nuxt** (everything is auto-imported)\n\n```vue\n\u003cscript setup\u003e\nimport { ref, onMounted } from 'vue'\nimport { useGlobalLoader } from 'vue-global-loader'\n\nconst { destroyLoader } = useGlobalLoader()\n\nconst data = ref(null)\n\nonMounted(async () =\u003e {\n  try {\n    data.value = await fetchDashboardData()\n    destroyLoader() // ...destroy loader, UI is ready\n  } catch (err) {\n    console.error(err)\n    // Handle error\n  }\n})\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cdiv v-if=\"data\"\u003e\n    \u003c!-- ... --\u003e\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n\n## Customization\n\n### Spinners\n\nThis package ships with 8 spinners that should cover most use cases:\n\n|                                                     | Import                                                                |\n| --------------------------------------------------- | --------------------------------------------------------------------- |\n| ![spinner](https://svgur.com/i/zKJ.svg)             | `import CircleSpinner from 'vue-global-loader/CircleSpinner.vue'`     |\n| ![ring-spinner](https://svgur.com/i/zJk.svg)        | `import RingSpinner from 'vue-global-loader/RingSpinner.vue'`         |\n| ![ring-dot-spinner](https://svgur.com/i/zKc.svg)    | `import RingDotSpinner from 'vue-global-loader/RingDotSpinner.vue'`   |\n| ![circle-bars-spinner](https://svgur.com/i/zHt.svg) | `import RingBarsSpinner from 'vue-global-loader/RingBarsSpinner.vue'` |\n| ![pulse-spinner](https://svgur.com/i/zKK.svg)       | `import PulseSpinner from 'vue-global-loader/PulseSpinner.vue'`       |\n| ![dots-spinner](https://svgur.com/i/zKf.svg)        | `import DotsSpinner from 'vue-global-loader/DotsSpinner.vue'`         |\n| ![bars-spinner](https://svgur.com/i/zHu.svg)        | `import BarsSpinner from 'vue-global-loader/BarsSpinner.vue'`         |\n| ![wave-spinner](https://svgur.com/i/zJ6.svg)        | `import WaveSpinner from 'vue-global-loader/WaveSpinner.vue'`         |\n\nImport the one you prefer and pass it to the default slot:\n\n\u003e :bulb: No need to state the imports if using **Nuxt** (everything is auto-imported)\n\n```vue\n\u003cscript setup\u003e\nimport GlobalLoader from 'vue-global-loader/GlobalLoader.vue'\nimport PulseSpinner from 'vue-global-loader/PulseSpinner.vue'\n\u003c/script\u003e\n\n\u003ctemplate\u003e\n  \u003cGlobalLoader\u003e\n    \u003cPulseSpinner /\u003e\n  \u003c/GlobalLoader\u003e\n\n  \u003c!-- RouterView, NuxtLayout, NuxtPage... --\u003e\n\u003c/template\u003e\n```\n\nEach spinner already has its own CSS and inherits the `foregroundColor` option specified in your config or scoped options.\n\nIf you think the spinner size is too big, add a class or inline styles to it and they'll be forwarded to the root `svg` element:\n\n```vue\n\u003ctemplate\u003e\n  \u003cGlobalLoader\u003e\n    \u003cPulseSpinner style=\"width: 60px\" /\u003e\n  \u003c/GlobalLoader\u003e\n\u003c/template\u003e\n```\n\n#### Custom Spinners\n\nTo use your own spinner, pass a custom SVG (or whatever) to the default slot:\n\n```vue\n\u003ctemplate\u003e\n  \u003cGlobalLoader\u003e\n    \u003csvg\n      class=\"MySpinner\"\n      viewBox=\"0 0 100 100\"\n      xmlns=\"http://www.w3.org/2000/svg\"\n    \u003e\n      \u003c!-- Inner markup --\u003e\n    \u003c/svg\u003e\n  \u003c/GlobalLoader\u003e\n\n  \u003c!-- RouterView, NuxtLayout, NuxtPage... --\u003e\n\u003c/template\u003e\n\n\u003cstyle\u003e\n.MySpinner {\n  fill: var(--v-gl-fg-color); /* Value of the 'foregroundColor' prop */\n  width: 100px;\n  height: 100px;\n\n  @media (min-width: 768px) {\n    width: 160px;\n    height: 100px;\n  }\n}\n\u003c/style\u003e\n```\n\n### Options\n\n| Prop                  | Type     | Description                                                        | Default      |\n| --------------------- | -------- | ------------------------------------------------------------------ | ------------ |\n| `screenReaderMessage` | `string` | Message to announce when displaying the loader.                    | `Loading`    |\n| `transitionDuration`  | `number` | Enter/leave fade transition duration in ms. Set `0` to disable it. | `250`        |\n| `foregroundColor`     | `string` | Color of the spinner.                                              | `#000`       |\n| `backgroundColor`     | `string` | Background color of the loading screen.                            | `#fff`       |\n| `backgroundOpacity`   | `number` | Background opacity of the loading screen.                          | `1`          |\n| `backgroundBlur`      | `number` | Background blur of the loading screen.                             | `0`          |\n| `zIndex`              | `number` | Z-index of the loading screen.                                     | `2147483647` |\n\n#### Default Options\n\nTo customize defaults, pass the options to the `globalLoader` plugin (if using Vite):\n\n**main.js**\n\n```js\napp.use(globalLoader, {\n  background: '#000',\n  foreground: '#fff',\n  screenReaderMessage: 'Loading, please wait...'\n})\n```\n\nOr to the `globalLoader` config key (if using Nuxt):\n\n**nuxt.config.ts**\n\n```ts\nexport default defineNuxtConfig({\n  modules: ['vue-global-loader/nuxt']\n  globalLoader: {\n    background: '#000',\n    foreground: '#fff',\n    screenReaderMessage: 'Loading, please wait...'\n  }\n})\n```\n\n#### Scoped Options\n\nYou can define options for a specific loader via `useGlobalLoader` options, only the loader triggered here (when calling `displayLoader`) will have a different `backgroundOpacity` and `screenReaderMessage`):\n\n```ts\nconst { displayLoader, destroyLoader } = useGlobalLoader({\n  backgroundOpacity: 0.5,\n  screenReaderMessage: 'Redirecting to payment page, please wait...'\n})\n```\n\n#### Updating default options\n\nFor convenience, you can set new defaults from anywhere in your Vue app using `updateOptions`:\n\n\u003e :bulb: No need to state the imports if using **Nuxt** (everything is auto-imported)\n\n```vue\n\u003cscript setup\u003e\nimport { watch } from 'vue'\nimport { useGlobalLoader } from 'vue-global-loader'\nimport { useStore } from '@/stores/my-store'\nimport messages from '@/locales/messages.json'\n\nconst store = useStore()\n\nconst { updateOptions } = useGlobalLoader()\n\n// Kept in sync with an hypotetical store\nwatch(\n  () =\u003e store.locale,\n  (newLocale) =\u003e {\n    updateOptions({\n      screenReaderMessage: messages[newLocale].loading\n    })\n  },\n  { immediate: true }\n)\n\u003c/script\u003e\n```\n\n### Callbacks / Transitions Lifecycle\n\nThe `GlobalLoader` lifecycle is handled using Vue's [Transition](https://vuejs.org/guide/built-ins/transition.html) hooks. For convenience, `displayLoader` and `destroyLoader` include some syntactic sugar to make it easier to execute code after the fade transition is completed.\n\n#### `displayLoader`\n\nThis function returns a promise that resolves after the enter transition is completed or cancelled.\n\n```ts\nconst { displayLoader } = useGlobalLoader()\nconst router = useRouter()\n\nawait displayLoader() // Wait for the fade transition to complete...\nawait signOut() // ...mutate the underlying UI\nrouter.push('/') // ...navigate to the homepage and call 'destroyLoader' there\n```\n\n#### `destroyLoader`\n\nThis function doesn't return a promise but instead, it accepts a callback that is executed after the loader is destroyed.\n\n```ts\nconst { destroyLoader } = useGlobalLoader()\n\ndestroyLoader(() =\u003e {\n  console.log('Loader destroyed')\n})\n```\n\n## API\n\n```ts\ninterface GlobalLoaderOptions {\n  screenReaderMessage: string\n  transitionDuration: number\n  foregroundColor: string\n  backgroundColor: string\n  backgroundOpacity: number\n  backgroundBlur: number\n  zIndex: number\n}\n\ndeclare function useGlobalLoader(\n  scopedOptions?: Partial\u003cGlobalLoaderOptions\u003e\n): {\n  displayLoader: () =\u003e Promise\u003cvoid\u003e\n  destroyLoader: (onDestroyed?: () =\u003e void) =\u003e void\n  updateOptions: (newOptions: Partial\u003cGlobalLoaderOptions\u003e) =\u003e void\n  options: Readonly\u003cReactive\u003cGlobalLoaderOptions\u003e\u003e\n  isLoading: Readonly\u003cRef\u003cboolean\u003e\u003e\n}\n```\n\n## Further Notes\n\n### When to use it\n\nUse it when you think it's better for the user to not interact with the rest of the app or to not see what's happening in the UI while an expensive async operation **initiated by the user** is taking place.\n\n### When to not use it\n\n- To display a loader while your app JS is loading. Use the [SPA Loading Templates](#spa-loading-templates) in plain HTML for that (see below).\n- Server-side rendered pages: they are already meant to send the proper content to the client and avoid spinners.\n- Non-critical async operations that are quick and obvious, in such case a local loader is better (e.g. spinner in the newsletter form submit button).\n- Async operations meant to feed the content of small sub-components, in such case [Suspense](https://vuejs.org/guide/built-ins/suspense.html) is preferred.\n\n## SPA Loading Templates\n\nFor convenience, ready-made HTML templates for each spinner shipped with this package are available in [this folder](https://github.com/smastrom/vue-global-loader/tree/main/spa-loading-templates).\n\nThey can be used to display a global loader that has the same appearance of the one used in your app to be displayed while the app JS is loading.\n\n### Vite\n\nCopy/paste the file content markup as a direct child of the `body` in the `index.html` file and remove it in a top-level (App.vue) _onMounted_ hook via `document.getElementById('spa_loading_template').remove()`.\n\n### Nuxt\n\nDownload the html file you prefer, rename it to `spa-loading-template.html` and place it in `@/app/spa-loading-template.html`.\n\n## Thanks\n\n[@n3r4zzurr0](https://github.com/n3r4zzurr0) for the awesome spinners.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmastrom%2Fvue-global-loader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsmastrom%2Fvue-global-loader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsmastrom%2Fvue-global-loader/lists"}