├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── docs ├── .npmrc ├── public │ ├── robots.txt │ ├── cover.png │ ├── gsap.png │ ├── favicon.ico │ └── nuxt.svg ├── tsconfig.json ├── content │ ├── 2.usage │ │ ├── _dir.yml │ │ ├── 1.playground.md │ │ ├── 6.composable.md │ │ ├── 4.presets.md │ │ ├── 2.examples.md │ │ ├── 7.v-if.md │ │ ├── 5.timeline.md │ │ └── 3.modifiers.md │ ├── 3.Information │ │ ├── _dir.yml │ │ ├── 2.known-issues.md │ │ └── 1.gsap-plugins.md │ ├── 1.installation │ │ ├── _dir.yml │ │ ├── 1.getting-started.md │ │ ├── 2.configuration.md │ │ ├── 4.vue.only.md │ │ └── 3.Philosophy.md │ └── 0.index.md ├── assets │ └── css │ │ ├── tailwind.css │ │ ├── global.css │ │ ├── terminal.css │ │ └── shiki.css ├── renovate.json ├── .gitignore ├── tailwind.config.js ├── .eslintrc.cjs ├── components │ ├── DemoStateToggle.vue │ ├── DemoComponent.vue │ ├── DemoParallax.vue │ ├── DemoCard.vue │ ├── DemoPinned.vue │ ├── DemoStateArray.vue │ ├── BoxComponent.vue │ ├── DemoTimelineCard.vue │ ├── DemoTextflow.vue │ └── Snippet.vue ├── package.json ├── nuxt.config.ts ├── app.config.ts ├── README.md ├── tokens.config.ts └── pages │ └── playground.vue ├── .npmrc ├── playground ├── tsconfig.json ├── server │ └── tsconfig.json ├── app.vue ├── package.json └── nuxt.config.ts ├── .vscode └── settings.json ├── src ├── runtime │ ├── server │ │ └── tsconfig.json │ ├── types │ │ └── Preset.ts │ ├── styles │ │ └── vgsap.css │ ├── utils │ │ ├── utils.ts │ │ └── entrance-presets.ts │ ├── vue.ts │ ├── nuxt.ts │ ├── components │ │ └── GSAPTransition.vue │ └── plugin.ts └── module.ts ├── test ├── fixtures │ └── basic │ │ ├── package.json │ │ ├── app.vue │ │ └── nuxt.config.ts └── basic.test.ts ├── tsconfig.json ├── .editorconfig ├── .prettierrc ├── eslint.config.mjs ├── .gitignore ├── package.json ├── README.md └── CHANGELOG.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/.npmrc: -------------------------------------------------------------------------------- 1 | strict-peer-dependencies=false 2 | -------------------------------------------------------------------------------- /docs/public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: * -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.experimental.useFlatConfig": true 3 | } 4 | -------------------------------------------------------------------------------- /docs/content/2.usage/_dir.yml: -------------------------------------------------------------------------------- 1 | title: 'Usage' 2 | icon: heroicons-outline:bookmark-alt 3 | -------------------------------------------------------------------------------- /playground/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../.nuxt/tsconfig.server.json" 3 | } 4 | -------------------------------------------------------------------------------- /docs/assets/css/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; -------------------------------------------------------------------------------- /docs/content/3.Information/_dir.yml: -------------------------------------------------------------------------------- 1 | title: 'Information' 2 | icon: heroicons-outline:bookmark-alt 3 | -------------------------------------------------------------------------------- /docs/public/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holux-design/v-gsap-nuxt/HEAD/docs/public/cover.png -------------------------------------------------------------------------------- /docs/public/gsap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holux-design/v-gsap-nuxt/HEAD/docs/public/gsap.png -------------------------------------------------------------------------------- /src/runtime/server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../.nuxt/tsconfig.server.json", 3 | } 4 | -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holux-design/v-gsap-nuxt/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /playground/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/fixtures/basic/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "basic", 4 | "type": "module" 5 | } 6 | -------------------------------------------------------------------------------- /docs/content/1.installation/_dir.yml: -------------------------------------------------------------------------------- 1 | icon: ph:star-duotone 2 | navigation.redirect: /installation/getting-started 3 | -------------------------------------------------------------------------------- /test/fixtures/basic/app.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /docs/assets/css/global.css: -------------------------------------------------------------------------------- 1 | .highlight-vue { 2 | max-width: calc(100vw - (3 * var(--elements-container-padding-mobile))); 3 | overflow: auto; 4 | } -------------------------------------------------------------------------------- /docs/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["github>nuxt/renovate-config-nuxt"], 3 | "lockFileMaintenance": { 4 | "enabled": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/runtime/types/Preset.ts: -------------------------------------------------------------------------------- 1 | export type Preset = { 2 | name: string 3 | modifiers: string 4 | value?: string | object | object[] 5 | } 6 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | -------------------------------------------------------------------------------- /test/fixtures/basic/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import MyModule from '../../../src/module' 2 | 3 | export default defineNuxtConfig({ 4 | modules: [ 5 | MyModule, 6 | ], 7 | }) 8 | -------------------------------------------------------------------------------- /docs/assets/css/terminal.css: -------------------------------------------------------------------------------- 1 | .terminal { 2 | max-height: 100px; 3 | } 4 | 5 | .terminal .header { 6 | min-height: 36px; 7 | } 8 | 9 | .terminal .line { 10 | margin-top: 4px; 11 | } -------------------------------------------------------------------------------- /docs/content/2.usage/1.playground.md: -------------------------------------------------------------------------------- 1 | # Playground 2 | 3 | You can find a number of snippets and showcases in the `v-gsap` playground 4 | 5 | Go to Playground -------------------------------------------------------------------------------- /src/runtime/styles/vgsap.css: -------------------------------------------------------------------------------- 1 | *[data-vgsap-from-invisible="true"]:not([data-vgsap-stagger="true"]) { 2 | opacity: 0; 3 | } 4 | *[data-vgsap-from-invisible="true"][data-vgsap-stagger="true"] > * { 5 | opacity: 0; 6 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | "exclude": [ 4 | "dist", 5 | "node_modules", 6 | "playground", 7 | ], 8 | "compilerOptions": { 9 | "noImplicitAny": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/runtime/utils/utils.ts: -------------------------------------------------------------------------------- 1 | export const uuidv4 = (): string => { 2 | return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, c => 3 | (+c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> +c / 4).toString(16), 4 | ) 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_size = 2 5 | indent_style = space 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /src/runtime/vue.ts: -------------------------------------------------------------------------------- 1 | import gsap from 'gsap' 2 | import { vGsapDirective } from './plugin' 3 | 4 | export const vGsapVue = (configOptions?) => { 5 | return vGsapDirective( 6 | 'vue', 7 | configOptions || {}, 8 | gsap.context(() => {}), 9 | null, 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /docs/content/3.Information/2.known-issues.md: -------------------------------------------------------------------------------- 1 | # Known Issues 2 | 3 | - `v-gsap.timeline.pinned` can cause an Error `500 ... insertBefore ...`. This issue will be fixed in the future, but in general it is a known issue with GSAP regarding DOM hierarchy. A quick fix is to wrap the whole element in another `
` -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "my-module-playground", 4 | "type": "module", 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate" 9 | }, 10 | "dependencies": { 11 | "nuxt": "^3.14.1592" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | modules: ['../src/module'], 3 | devtools: { enabled: true }, 4 | compatibilityDate: '2024-11-28', 5 | 6 | vgsap: { 7 | presets: [ 8 | { 9 | name: 'spin', 10 | modifiers: 'infinitely.to', 11 | value: { rotate: '90deg', ease: 'linear' }, 12 | }, 13 | ], 14 | }, 15 | }) 16 | -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ['class'], 4 | safelist: ['dark'], 5 | // SHADCN :: END 6 | prefix: '', 7 | mode: 'jit', 8 | content: [ 9 | 'components/**/*.{html,js,vue}', 10 | 'content/**/*.{md}', 11 | 'pages/**/*.vue', 12 | ], 13 | theme: { 14 | extend: {}, 15 | }, 16 | plugins: [], 17 | } 18 | -------------------------------------------------------------------------------- /src/runtime/nuxt.ts: -------------------------------------------------------------------------------- 1 | import gsap from 'gsap' 2 | import { vGsapDirective } from './plugin' 3 | import { defineNuxtPlugin, useRuntimeConfig } from '#app' 4 | 5 | export default defineNuxtPlugin((nuxtApp) => { 6 | let resizeListener 7 | 8 | nuxtApp.vueApp.directive( 9 | 'gsap', 10 | vGsapDirective( 11 | 'nuxt', 12 | useRuntimeConfig().public.vgsap ?? {}, 13 | null, 14 | resizeListener, 15 | ), 16 | ) 17 | }) 18 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "bracketSpacing": true, 4 | "htmlWhitespaceSensitivity": "css", 5 | "insertPragma": false, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": true, 8 | "printWidth": 80, 9 | "proseWrap": "always", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "semi": false, 13 | "singleQuote": true, 14 | "tabWidth": 2, 15 | "trailingComma": "all", 16 | "useTabs": false 17 | } 18 | -------------------------------------------------------------------------------- /docs/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@nuxt/eslint-config'], 4 | ignorePatterns: [ 5 | 'dist', 6 | 'node_modules', 7 | '.output', 8 | '.nuxt', 9 | ], 10 | rules: { 11 | 'vue/max-attributes-per-line': 'off', 12 | 'vue/multi-word-component-names': 'off', 13 | '@typescript-eslint/no-explicit-any': 'off', 14 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 15 | 'no-unsafe-optional-chaining': 'off', 16 | }, 17 | } 18 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { describe, it, expect } from 'vitest' 3 | import { setup, $fetch } from '@nuxt/test-utils/e2e' 4 | 5 | describe('ssr', async () => { 6 | await setup({ 7 | rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url)), 8 | }) 9 | 10 | it('renders the index page', async () => { 11 | // Get response to a server-rendered page with `$fetch`. 12 | const html = await $fetch('/') 13 | expect(html).toContain('
basic
') 14 | }) 15 | }) 16 | -------------------------------------------------------------------------------- /docs/assets/css/shiki.css: -------------------------------------------------------------------------------- 1 | .Snippet .shiki { 2 | border-radius: 8px; 3 | border: 1px solid rgba(255,255,255,0.1) !important; 4 | background-color: rgba(255,255,255,0.05) !important; 5 | } 6 | 7 | .light .Snippet .shiki { 8 | border: 1px solid rgba(255,255,255,0.1) !important; 9 | background-color: rgba(255,255,255,0.05) !important; 10 | filter:invert(1) saturate(2); 11 | } 12 | 13 | .Snippet .shiki code { 14 | display: flex; 15 | flex-direction: column; 16 | 17 | padding: 20px 32px; 18 | overflow:auto; 19 | } 20 | 21 | .Snippet .shiki code * { 22 | font-size: 12px; 23 | } -------------------------------------------------------------------------------- /docs/components/DemoStateToggle.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 18 | -------------------------------------------------------------------------------- /docs/components/DemoComponent.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 17 | 18 | 25 | -------------------------------------------------------------------------------- /docs/content/1.installation/1.getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | `v-gsap-nuxt` is a batteries-included module for easy access to the power of GSAP 4 | 5 | 6 | ## Auto install 7 | 8 | 9 | ```bash [npx] 10 | npx nuxi module add v-gsap-nuxt 11 | ``` 12 | 13 | 14 | ## Install manually 15 | 16 | 1. Install from npm 17 | 18 | ::code-group 19 | 20 | ```bash [npm] 21 | npm i v-gsap-nuxt 22 | ``` 23 | :: 24 | 25 | 2. Add module to your `nuxt.config.ts` 26 | 27 | ::code-group 28 | 29 | ```ts [nuxt.config.ts] 30 | modules: [ 31 | 'v-gsap-nuxt' 32 | ] 33 | ``` 34 | :: -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docus-starter", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "nuxi dev", 7 | "build": "nuxi build", 8 | "generate": "nuxi generate", 9 | "preview": "nuxi preview", 10 | "lint": "eslint ." 11 | }, 12 | "devDependencies": { 13 | "@nuxt-themes/docus": "latest", 14 | "@nuxt/devtools": "^1.6.0", 15 | "@nuxt/eslint-config": "^0.6.1", 16 | "@nuxtjs/tailwindcss": "^6.12.2", 17 | "@types/node": "^22.9.0", 18 | "eslint": "^9.14.0", 19 | "nuxt": "^3.14.159", 20 | "pinceau": "^0.15.4", 21 | "sass": "^1.81.0", 22 | "shiki": "^1.24.0" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docs/content/2.usage/6.composable.md: -------------------------------------------------------------------------------- 1 | # Composable 2 | 3 | By default, GSAP is also exported as composable `useGSAP()` 4 | 5 | This allows for all cases where the directive doesnt work to just use regular GSAP from the ` 17 | ``` 18 | 19 | ## Disabling composable 20 | 21 | If you are experiencing issues due to other already existing imports of gsap, you can disable the auto-import of the composable in `nuxt.config.ts` via the `composable: ` property [See more](/installation/configuration) 22 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { createConfigForNuxt } from '@nuxt/eslint-config/flat' 3 | 4 | // Run `npx @eslint/config-inspector` to inspect the resolved config interactively 5 | export default createConfigForNuxt({ 6 | features: { 7 | // Rules for module authors 8 | tooling: true, 9 | // Rules for formatting 10 | stylistic: true, 11 | }, 12 | dirs: { 13 | src: ['./playground'], 14 | }, 15 | }).append({ 16 | rules: { 17 | '@typescript-eslint/no-explicit-any': 'off', 18 | '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', 19 | 'no-unsafe-optional-chaining': 'off', 20 | 'vue/multi-word-component-names': 'off', 21 | '@stylistic/no-tabs': 'off', 22 | '@typescript-eslint/no-unused-vars': 'off', 23 | }, 24 | }) 25 | -------------------------------------------------------------------------------- /docs/components/DemoParallax.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # Logs 5 | *.log* 6 | 7 | # Temp directories 8 | .temp 9 | .tmp 10 | .cache 11 | 12 | # Yarn 13 | **/.yarn/cache 14 | **/.yarn/*state* 15 | 16 | # Generated dirs 17 | dist 18 | 19 | # Nuxt 20 | .nuxt 21 | .output 22 | .data 23 | .vercel_build_output 24 | .build-* 25 | .netlify 26 | 27 | # Env 28 | .env 29 | 30 | # Testing 31 | reports 32 | coverage 33 | *.lcov 34 | .nyc_output 35 | 36 | # VSCode 37 | .vscode/* 38 | !.vscode/settings.json 39 | !.vscode/tasks.json 40 | !.vscode/launch.json 41 | !.vscode/extensions.json 42 | !.vscode/*.code-snippets 43 | 44 | # Intellij idea 45 | *.iml 46 | .idea 47 | 48 | # OSX 49 | .DS_Store 50 | .AppleDouble 51 | .LSOverride 52 | .AppleDB 53 | .AppleDesktop 54 | Network Trash Folder 55 | Temporary Items 56 | .apdisk 57 | 58 | .vercel -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | // https://github.com/nuxt-themes/docus 3 | extends: ['@nuxt-themes/docus'], 4 | 5 | modules: ['../src/module', '@nuxtjs/tailwindcss'], 6 | devtools: { enabled: true }, 7 | 8 | css: [ 9 | '~/assets/css/terminal.css', 10 | '~/assets/css/shiki.css', 11 | '~/assets/css/global.css', 12 | ], 13 | compatibilityDate: '2024-10-24', 14 | 15 | nitro: { 16 | preset: 'vercel', 17 | output: { 18 | dir: '../.vercel/output', 19 | }, 20 | }, 21 | 22 | vgsap: { 23 | presets: [ 24 | { 25 | name: 'rotate', 26 | modifiers: 'inifinitely.to', 27 | value: { 28 | rotate: '360deg', 29 | ease: 'linear', 30 | duration: 10, 31 | }, 32 | }, 33 | ], 34 | }, 35 | }) 36 | -------------------------------------------------------------------------------- /docs/content/3.Information/1.gsap-plugins.md: -------------------------------------------------------------------------------- 1 | # Adding GSAP Plugins 2 | 3 | If you want to add your own GSAP Plugins (like `EasePack`, `PhysicsPropsPlugin`, 4 | etc) you can do so by registering them with the composable `useGSAP()`. 5 | 6 | Since it shares the actual gsap instance from the module, plugins only need to 7 | be registered once - preferably as high in the app structure as needed. 8 | 9 | ::code-group 10 | 11 | ```vue [app.vue] 12 | 19 | ``` 20 | 21 | :: 22 | 23 | --- 24 | 25 | The following plugins are imported by default as part of the modules featureset 26 | and therefore are not needed to import manually: 27 | 28 | - `ScrollTrigger` 29 | - `ScrollToPlugin` 30 | - `Draggable` 31 | - `TextPlugin` 32 | -------------------------------------------------------------------------------- /docs/content/1.installation/2.configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | In general, no config is needed. However, there are some options you can configure: 4 | 5 | ::code-group 6 | 7 | ```ts [nuxt.config.ts] 8 | vgsap: { 9 | presets: [], 10 | breakpoint: 768, 11 | scroller: '', 12 | composable: true 13 | } 14 | ``` 15 | 16 | :: 17 | 18 | - `presets` lets you define reoccuring animations. [See more](/usage/presets) 19 | - `breakpoint` `number` lets you define a custom breakpoint for `.desktop` and `.mobile` modifier 20 | - `scroller` `string` as `selector` lets you define a custom scroller that will be applied to all scrollTriggers 21 | - This is needed if your content is wrapped in an artificial scroll wrapper, instead of the default document/body 22 | - `composable` `boolean` `default: true` lets you decide wether or not to load GSAP as a composable as well [See more](/usage/composable) 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | lint: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - run: corepack enable 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: 20 21 | 22 | - name: Install dependencies 23 | run: npx nypm@latest i 24 | 25 | - name: Lint 26 | run: npm run lint 27 | 28 | test: 29 | runs-on: ubuntu-latest 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | - run: corepack enable 34 | - uses: actions/setup-node@v4 35 | with: 36 | node-version: 20 37 | 38 | - name: Install dependencies 39 | run: npx nypm@latest i 40 | 41 | - name: Playground prepare 42 | run: npm run prepare:playground 43 | 44 | - name: Test 45 | run: npm run test 46 | -------------------------------------------------------------------------------- /docs/app.config.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/nuxt-themes/docus/blob/main/nuxt.schema.ts 2 | export default defineAppConfig({ 3 | docus: { 4 | title: 'v-gsap-nuxt', 5 | description: 'GSAP as a directive for Nuxt', 6 | image: 'https://v-gsap-nuxt.vercel.app/cover.png', 7 | socials: { 8 | github: 'holux-design/v-gsap-nuxt', 9 | nuxt: { 10 | label: 'Nuxt', 11 | icon: 'simple-icons:nuxtdotjs', 12 | href: 'https://nuxt.com', 13 | }, 14 | }, 15 | github: { 16 | dir: '.starters/default/content', 17 | branch: 'main', 18 | repo: 'docus', 19 | owner: 'nuxt-themes', 20 | edit: true, 21 | }, 22 | aside: { 23 | level: 0, 24 | collapsed: false, 25 | exclude: [], 26 | }, 27 | main: { 28 | padded: true, 29 | fluid: true, 30 | }, 31 | header: { 32 | logo: false, 33 | showLinkIcon: true, 34 | exclude: [], 35 | fluid: true, 36 | }, 37 | }, 38 | }) 39 | -------------------------------------------------------------------------------- /src/runtime/utils/entrance-presets.ts: -------------------------------------------------------------------------------- 1 | export const entrancePresets = [ 2 | { 3 | name: 'slide-left', 4 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 5 | value: [{ x: -32 }, { x: 0 }], 6 | }, 7 | { 8 | name: 'slide-right', 9 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 10 | value: [{ x: 32 }, { x: 0 }], 11 | }, 12 | { 13 | name: 'slide-top', 14 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 15 | value: [{ y: -32 }, { y: 0 }], 16 | }, 17 | { 18 | name: 'slide-bottom', 19 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 20 | value: [{ y: 32 }, { y: 0 }], 21 | }, 22 | { 23 | name: 'scale', 24 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 25 | value: [{ scale: 0.75 }, { scale: 1 }], 26 | }, 27 | { 28 | name: 'scale-full', 29 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 30 | value: [{ scale: 0 }, { scale: 1 }], 31 | }, 32 | { 33 | name: 'fade', 34 | modifiers: 'whenVisible.fromInvisible.once.fromTo', 35 | value: [{ autoAlpha: 0 }, { autoAlpha: 1 }], 36 | }, 37 | ] 38 | -------------------------------------------------------------------------------- /docs/components/DemoCard.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 34 | -------------------------------------------------------------------------------- /docs/public/nuxt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Docus Starter 2 | 3 | Starter template for [Docus](https://docus.dev). 4 | 5 | ## Clone 6 | 7 | Clone the repository (using `nuxi`): 8 | 9 | ```bash 10 | npx nuxi init -t themes/docus 11 | ``` 12 | 13 | ## Setup 14 | 15 | Install dependencies: 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | ## Development 22 | 23 | ```bash 24 | yarn dev 25 | ``` 26 | 27 | ## Edge Side Rendering 28 | 29 | Can be deployed to Vercel Functions, Netlify Functions, AWS, and most Node-compatible environments. 30 | 31 | Look at all the available presets [here](https://v3.nuxtjs.org/guide/deploy/presets). 32 | 33 | ```bash 34 | yarn build 35 | ``` 36 | 37 | ## Static Generation 38 | 39 | Use the `generate` command to build your application. 40 | 41 | The HTML files will be generated in the .output/public directory and ready to be deployed to any static compatible hosting. 42 | 43 | ```bash 44 | yarn generate 45 | ``` 46 | 47 | ## Preview build 48 | 49 | You might want to preview the result of your build locally, to do so, run the following command: 50 | 51 | ```bash 52 | yarn preview 53 | ``` 54 | 55 | --- 56 | 57 | For a detailed explanation of how things work, check out [Docus](https://docus.dev). 58 | -------------------------------------------------------------------------------- /docs/content/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Home 3 | navigation: false 4 | layout: page 5 | main: 6 | fluid: false 7 | --- 8 | 9 | :ellipsis{right=0px width=75% blur=150px} 10 | 11 | ::block-hero 12 | --- 13 | cta: 14 | - Get started 15 | - /installation/getting-started 16 | secondary: 17 | - See Demo → 18 | - /playground 19 | --- 20 | 21 | #title 22 | 23 |
24 |
25 | 26 | + 27 | 28 |
29 | 30 |
31 | GSAP hustle without
32 | the GSAP hassle 33 |
34 |
35 | 36 | #description 37 | Write [GSAP](https://gsap.com/) Animations inline, like a human being. 38 | 39 | 40 | ```vue 41 |
42 | ``` 43 | 44 | 45 | 46 | 47 | 48 | #support 49 | ::terminal 50 | --- 51 | content: 52 | - npx nuxi module add v-gsap-nuxt 53 | --- 54 | :: 55 | :: 56 | -------------------------------------------------------------------------------- /docs/components/DemoPinned.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 27 | 28 | 49 | -------------------------------------------------------------------------------- /docs/content/2.usage/4.presets.md: -------------------------------------------------------------------------------- 1 | # Presets 2 | 3 | For reoccuring animations you can define presets in `nuxt.config.ts` 4 | 5 | ## Add option in config 6 | 7 | To add a preset, create an array `presets` in the config options for `vgsap` 8 | 9 | ::code-group 10 | 11 | ```ts [nuxt.config.ts] 12 | vgsap: { 13 | presets: [] 14 | } 15 | ``` 16 | :: 17 | 18 | ## Add a preset 19 | 20 | A preset is of the following type 21 | 22 | ```ts 23 | { 24 | name: string; 25 | modifiers: string; 26 | value: any; 27 | } 28 | ``` 29 | 30 | - `name` is what is used to access the preset later 31 | - `modifiers` refers to whats usually the lefthand side of the directive entry. 32 | - only include what should follow after `v-gsap.`, separated by `.` 33 | - `value` is the actual parameter that is passed to the directive. 34 | 35 | Here's an example of an actual preset: 36 | 37 | ```ts 38 | vgsap: { 39 | presets: [ 40 | { 41 | name: 'stagger-right', 42 | modifiers: 'whenVisible.stagger.once.from', 43 | value: { autoAlpha: 0, x: -32 } 44 | } 45 | ] 46 | } 47 | ``` 48 | 49 | For better understanding - this is what the above preset represents: 50 | ```vue 51 |
52 | ``` 53 | 54 | ## Use the preset 55 | 56 | To use a preset, simply access it via the modifier `.preset` with the value being the name of the preset: 57 | 58 | ```vue 59 |
60 | ``` 61 | -------------------------------------------------------------------------------- /docs/content/1.installation/4.vue.only.md: -------------------------------------------------------------------------------- 1 | # Vue only 2 | 3 | Since Version `1.2.1` you can also use this module for Vue - instead of Nuxt. 4 | 5 | ::alert{type="info"} 6 | This feature is still experimental and to use with caution. 7 | Please report any issues on the [Github Issues Tab](https://github.com/holux-design/v-gsap-nuxt/issues) 8 | :: 9 | 10 | ## 1. Install package 11 | 12 | To use with Vue, do not use the auto-install command, since this is a nuxt-specific module install. Instead install the package itself through npm: 13 | 14 | ```bash [npx] 15 | npm i v-gsap-nuxt 16 | ``` 17 | 18 | ## 2. Import directive 19 | 20 | In the second step go to your `main.ts` file for your vue app, import the Vue plugin and add it to your app **after initialization and before mount**: 21 | 22 | ::code-group 23 | 24 | ```ts [main.ts] 25 | import { vGsapVue } from 'v-gsap-nuxt/vue'; 26 | 27 | // const app = createApp(App); 28 | app.directive('gsap', vGsapVue()); 29 | // app.mount('#app'); 30 | :: 31 | 32 | 33 | ## Configuration 34 | 35 | Since global configuration for nuxt happens in `nuxt.config.ts` which is not available in Vue, you can add these settings directly during initialization. All props are the same as with nuxt. 36 | 37 | ::code-group 38 | 39 | ```ts [main.ts] 40 | app.directive('gsap', vGsapVue({ 41 | presets: [], 42 | breakpoint: 768, 43 | scroller: '', 44 | composable: true 45 | })); 46 | :: 47 | 48 | [See reference](/installation/configuration) -------------------------------------------------------------------------------- /docs/components/DemoStateArray.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 44 | -------------------------------------------------------------------------------- /src/module.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineNuxtModule, 3 | addPlugin, 4 | createResolver, 5 | addImports, 6 | addComponent, 7 | } from '@nuxt/kit' 8 | import { defu } from 'defu' 9 | 10 | // Module options TypeScript interface definition 11 | export interface ModuleOptions { 12 | [key: string]: any 13 | } 14 | 15 | export default defineNuxtModule({ 16 | meta: { 17 | name: 'v-gsap-nuxt', 18 | configKey: 'vgsap', 19 | compatibility: { 20 | nuxt: '>=3.0.0', 21 | }, 22 | }, 23 | // Default configuration options of the Nuxt module 24 | defaults: {}, 25 | setup(_options, _nuxt) { 26 | const resolver = createResolver(import.meta.url) 27 | _nuxt.options.runtimeConfig.public.vgsap = defu( 28 | _nuxt.options.runtimeConfig.public.vgsap as any, 29 | { 30 | ..._options, 31 | }, 32 | ) 33 | 34 | _nuxt.options.css.push(resolver.resolve('./runtime/styles/vgsap.css')) 35 | 36 | // Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack` 37 | addPlugin(resolver.resolve('./runtime/nuxt')) 38 | 39 | if ( 40 | (_nuxt.options.runtimeConfig.public.vgsap as any)?.composable != false 41 | ) { 42 | addImports({ 43 | name: 'useGSAP', 44 | as: 'useGSAP', 45 | from: resolver.resolve('runtime/plugin'), // load composable from plugin 46 | }) 47 | } 48 | 49 | addComponent({ 50 | name: 'GSAPTransition', 51 | filePath: resolver.resolve('./runtime/components/GSAPTransition.vue'), 52 | }) 53 | }, 54 | }) 55 | -------------------------------------------------------------------------------- /src/runtime/components/GSAPTransition.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 68 | -------------------------------------------------------------------------------- /docs/components/BoxComponent.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 52 | 53 | 63 | -------------------------------------------------------------------------------- /docs/components/DemoTimelineCard.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 57 | -------------------------------------------------------------------------------- /docs/components/DemoTextflow.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 32 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-gsap-nuxt", 3 | "version": "1.4.4", 4 | "description": "GSAP Directive for Nuxt 3", 5 | "repository": "holux-design/v-gsap-nuxt", 6 | "homepage": "https://v-gsap-nuxt.vercel.app", 7 | "contributors": [ 8 | { 9 | "name": "Lukas Hofstätter (Holux Design)" 10 | } 11 | ], 12 | "author": { 13 | "name": "Lukas Hofstätter", 14 | "email": "office@holux-design.at" 15 | }, 16 | "keywords": [ 17 | "v-gsap", 18 | "gsap", 19 | "nuxt", 20 | "nuxt3", 21 | "animation", 22 | "motion", 23 | "nuxt-gsap", 24 | "gsap-nuxt", 25 | "gsap-directive" 26 | ], 27 | "license": "MIT", 28 | "type": "module", 29 | "exports": { 30 | ".": { 31 | "types": "./dist/types.d.ts", 32 | "import": "./dist/module.mjs", 33 | "require": "./dist/module.cjs" 34 | }, 35 | "./vue": { 36 | "import": "./dist/runtime/vue.js", 37 | "types": "./dist/runtime/vue.d.ts" 38 | } 39 | }, 40 | "main": "./dist/module.cjs", 41 | "types": "./dist/types.d.ts", 42 | "files": [ 43 | "dist" 44 | ], 45 | "scripts": { 46 | "prepack": "nuxt-module-build build", 47 | "prepare": "nuxt prepare", 48 | "dev:docs": "nuxi dev docs --host", 49 | "dev:check": "npm run lint:fix && npm run test", 50 | "build:docs": "cd docs && npm i && cd .. && nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare docs && nuxi build docs", 51 | "prepare:docs": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare docs", 52 | "dev:playground": "nuxi dev playground", 53 | "build:playground": "nuxi build playground", 54 | "prepare:playground": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxi prepare playground", 55 | "release": "npm run lint:fix && npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags", 56 | "lint": "eslint .", 57 | "test": "vitest run", 58 | "test:watch": "vitest watch", 59 | "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit", 60 | "lint:fix": "eslint . --fix" 61 | }, 62 | "dependencies": { 63 | "@nuxt/kit": "^3.14.1592", 64 | "defu": "^6.1.4", 65 | "gsap": "^3.12.5" 66 | }, 67 | "devDependencies": { 68 | "@nuxt/devtools": "^1.6.1", 69 | "@nuxt/eslint-config": "^0.7.2", 70 | "@nuxt/module-builder": "^0.8.4", 71 | "@nuxt/schema": "^3.14.1592", 72 | "@nuxt/test-utils": "^3.14.4", 73 | "@types/node": "latest", 74 | "changelogen": "^0.5.7", 75 | "eslint": "^9.15.0", 76 | "nuxt": "^3.14.1592", 77 | "typescript": "latest", 78 | "vitest": "^2.1.6", 79 | "vue-tsc": "^2.1.10" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/content/1.installation/3.Philosophy.md: -------------------------------------------------------------------------------- 1 | # Philosophy 2 | 3 | ## Why is `v-gsap` a thing? 4 | 5 | Animations are an essential today, since they greatly enhance the user experience of any app or website. With an evergrowing ecosystem of frameworks and libraries, we as developers find ourselves in a constant battle of choosing a technology to provide us with the best Developer Experience and the lowest barrier for our ADHD. 6 | 7 | GSAP is one of the most complete and grown-up animation libraries out there. But I always found myself adding animations _after_ finalising the UI, since there is a mental barrier to set up a function to initialize the animation timeline yadiyadiyada. 8 | Growing accustomed to directly writing inline with tailwind, the strive to do the same thing just as easily for animations grew. 9 | 10 | `v-gsap` attempts to solve this mental barrier issue by breaking down everyday animations into a comfortable and readable format. 11 | 12 | Animations like 13 | 14 | ``` 15 | v-gsap.whenVisible.stagger.once.from=... 16 | ``` 17 | 18 | show how easily read- and writeable this inline format is. 19 | 20 | Whenever possible, all the properties are named the same as we are familiar with GSAP. However, for some reoccuring usecases I took the freedom to create new names like `whenVisible` for ScrollTrigger, or `parallax` as a global term for translateY animations. 21 | 22 | ## Why choose over other libraries 23 | 24 | I'll be honest: v-gsap kinda grew out of jealousy of framer motion. It's syntax isnt especially natural, but the basic idea of writing animations inline is a big step in DX. 25 | 26 | Of course there is the direct framermotion-competitor in the nuxt ecosystem: vueuse/motion. One could argue it lacks feature richness and it's state based approach (just like framer-motion) is an overkill in many situations. If your html bloats due to directive use, then it fuels the problem it was trying to solve. 27 | 28 | One could also compare with tailwind animations, but speaking of feature richness, it's a nope. 29 | 30 | A new package that can be seen as an alternative is [Glaze](https://glaze.dev). I admire their approach, since it is the exact same thought as `v-gsap-nuxt` but in plain JS for all tech stacks to use. However, it's syntax can get quite overwhelming when dealing with more complex animations. Being bound to nuxt, v-gsap-nuxt can utilize modifiers to simplify these animations and therefore provide a slightly better DX. 31 | 32 | ## What it is not 33 | 34 | This plugin certainly is a convenient drop-in for quick and easy wow-effects. It certainly isnt suitable for more complex animations with multiple steps or custom state logic like popups. There are plans on the roadmap to include state-logic, but it is not yet clear how that fits the general philosophy. 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `v-gsap` for Nuxt (and Vue) 2 | 3 | [![npm version][npm-version-src]][npm-version-href] 4 | [![npm downloads][npm-downloads-src]][npm-downloads-href] 5 | [![License][license-src]][license-href] [![Nuxt][nuxt-src]][nuxt-href] 6 | 7 | [![](docs/public/cover.png)](https://v-gsap-nuxt.vercel.app/) (Click image to 8 | visit the docs) 9 | 10 | ## Features 11 | 12 | - 🚀 Smooth animations with [GSAP](https://gsap.com/) 13 | - 🖍️ Easy to use directive syntax with 14 | [`v-gsap`](https://v-gsap-nuxt.vercel.app/playground) 15 | - 📦 [`useGSAP()`](https://v-gsap-nuxt.vercel.app/usage/composable) composable 16 | for complex animations 17 | - 🪄 [``](https://v-gsap-nuxt.vercel.app/usage/v-if) to animate 18 | `v-if` 19 | - ⌨️ Powerful 20 | [entrance presets](https://v-gsap-nuxt.vercel.app/usage/modifiers#entrance) 21 | and [custom presets](https://v-gsap-nuxt.vercel.app/usage/presets) 22 | - 🧩 Full GSAP 23 | [Plugin extensibility](https://v-gsap-nuxt.vercel.app/information/gsap-plugins) 24 | 25 | ## Installation 26 | 27 | Install the module to your Nuxt application with one command: 28 | 29 | ```bash 30 | npx nuxi module add v-gsap-nuxt 31 | ``` 32 | 33 | That's it! You can now use `v-gsap` in your Nuxt app ✨ 34 | 35 | ### Want to use it with Vue? [Learn about Vue usage](https://v-gsap-nuxt.vercel.app/installation/vue.only) 36 | 37 | ## Docs 38 | 39 | ### 👁️ Find the full docs and examples here: [Documentation](https://v-gsap-nuxt.vercel.app/) 40 | 41 | --- 42 | 43 | ### GSAP Licensing 44 | 45 | GSAP is subject to its own licensing terms. Before incorporating GSAP with 46 | `v-gsap-nuxt` (as dependency), ensure you review and comply with the 47 | [GSAP Standard License](https://gsap.com/community/standard-license/). 48 | 49 | This module itself is licensed under the MIT License. 50 | 51 | --- 52 | 53 | ## Contribution 54 | 55 |
56 | Local development 57 | 58 | ```bash 59 | # Install dependencies 60 | npm install 61 | 62 | # Generate type stubs 63 | npm run dev:prepare 64 | 65 | # Develop with the playground 66 | npm run dev:playground 67 | # OR 68 | # Develop with the Docs 69 | npm run dev:docs 70 | 71 | # Build the playground 72 | npm run dev:build 73 | 74 | # Run ESLint 75 | npm run dev:check 76 | 77 | # Release new version 78 | npm run release 79 | ``` 80 | 81 |
82 | 83 | 84 | 85 | [npm-version-src]: 86 | https://img.shields.io/npm/v/v-gsap-nuxt/latest.svg?style=flat&colorA=020420&colorB=00DC82 87 | [npm-version-href]: https://npmjs.com/package/v-gsap-nuxt 88 | [npm-downloads-src]: 89 | https://img.shields.io/npm/dm/v-gsap-nuxt.svg?style=flat&colorA=020420&colorB=00DC82 90 | [npm-downloads-href]: https://npm.chart.dev/v-gsap-nuxt 91 | [license-src]: 92 | https://img.shields.io/npm/l/v-gsap-nuxt.svg?style=flat&colorA=020420&colorB=00DC82 93 | [license-href]: https://npmjs.com/package/v-gsap-nuxt 94 | [nuxt-src]: https://img.shields.io/badge/Nuxt-020420?logo=nuxt.js 95 | [nuxt-href]: https://nuxt.com 96 | -------------------------------------------------------------------------------- /docs/components/Snippet.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 66 | -------------------------------------------------------------------------------- /docs/content/2.usage/2.examples.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Here you'll find some use-cases and examples.
4 | [See it in action](/playground) 5 | 6 | ## Entrance 7 | 8 | [See reference](/usage/modifiers#whenvisible) 9 | 10 | ### Basic fade-in from left 11 | 12 | ```vue 13 |
14 | ``` 15 | 16 | ### Custom start / end 17 | 18 | ```vue 19 |
26 | ``` 27 | 28 | ### Entrance only single time 29 | 30 | ```vue 31 |
32 | ``` 33 | 34 | ### With `.entrance` 35 | 36 | ```vue 37 |
38 | ``` 39 | 40 | ::alert{type="info"} 41 | There are multiple presets for entrace [See details](/usage/modifiers#entrance) 42 | :: 43 | 44 | ### Debug with markers 45 | 46 | ```vue 47 |
48 | ``` 49 | 50 | --- 51 | 52 | ## Magnetic 53 | 54 | [See reference](/usage/modifiers#magnetic) 55 | 56 | ### Basic 57 | 58 | ```vue 59 | 60 | ``` 61 | 62 | ### Strong 63 | 64 | ```vue 65 | 66 | ``` 67 | 68 | --- 69 | 70 | ## Stagger 71 | 72 | [See reference](/usage/modifiers#stagger) 73 | 74 | ```html 75 |
    76 |
  • Option 1
  • 77 |
  • Option 2
  • 78 |
  • Option 3
  • 79 |
80 | ``` 81 | 82 | --- 83 | 84 | ## Animate Text 85 | 86 | [See reference](/usage/modifiers#animatetext) 87 | 88 | ### Using textContent 89 | 90 | ```html 91 |
Lorem Ipsum...
92 | ``` 93 | 94 | ### Using value 95 | 96 | ```html 97 |
98 | ``` 99 | 100 | ### Using value with other props 101 | 102 | ```html 103 |
104 | ``` 105 | 106 | ### slow / fast 107 | 108 | ```html 109 |
Lorem Ipsum...
110 | ``` 111 | 112 | --- 113 | 114 | ## Parallax 115 | 116 | [See reference](/usage/modifiers#parallax) 117 | 118 | ### Slower 119 | 120 | ```html 121 |
Lorem Ipsum...
122 | ``` 123 | 124 | ### Faster with custom speed 125 | 126 | ```html 127 |
Lorem Ipsum...
128 | ``` 129 | 130 | --- 131 | 132 | ## Hover 133 | 134 | [See reference](/usage/modifiers#whilehover) 135 | 136 | ### Basic 137 | 138 | ```html 139 |
140 | ``` 141 | 142 | ### No reverse 143 | 144 | ```html 145 |
146 | ``` 147 | 148 | --- 149 | 150 | ## Differing Mobile / Desktop 151 | 152 | [See reference](/usage/modifiers#mobile) 153 | 154 | ### Desktop only 155 | 156 | ```html 157 |
158 | ``` 159 | 160 | ### Desktop + Mobile differing 161 | 162 | ```html 163 |
167 | ``` 168 | 169 | --- 170 | 171 | ## Draggable 172 | 173 | [See reference](/usage/modifiers#draggable) 174 | 175 | ### Basic (within parent) 176 | 177 | ```html 178 |
179 | ``` 180 | 181 | ### Basic (whole body) 182 | 183 | ```html 184 |
185 | ``` 186 | 187 | ### Horizontal only 188 | 189 | ```html 190 |
191 | ``` 192 | 193 | ### Vertical only (within body) 194 | 195 | ```html 196 |
197 | ``` 198 | 199 | ### Rotation 200 | 201 | ```html 202 |
203 | ``` 204 | 205 | --- 206 | -------------------------------------------------------------------------------- /docs/content/2.usage/7.v-if.md: -------------------------------------------------------------------------------- 1 | # With `v-if` 2 | 3 | A Bonus feature of this module is the `` component that allows 4 | you to use GSAP transitions on `v-if`/`v-show` elements. 5 | 6 | ```vue 7 | 8 |
Hello World
9 |
10 | ``` 11 | 12 | By default, the wrapped content will animate `opacity` with a (gsap-)default 13 | duration of `0.5s`. 14 | 15 | You can add custom properties to the `hidden` object to customize the animation. 16 | These values will be merged with the default opacity behaviour, so opacity 17 | doesnt need to be specified. 18 | 19 | ```vue 20 | 21 |
Hello World
22 |
23 | ``` 24 | 25 | ## Multiple elements 26 | 27 | In addition to the `` component, Vue provides a `` 28 | for transitioning multiple elements. Mostly this is done with `v-for` to show 29 | the appearance of a list of items. 30 | 31 | We utilise Vue's default group component by using the `group` flag on 32 | ``. 33 | 34 | ```vue 35 | 36 |
  • 40 | {{ item.name }} 41 |
  • 42 |
    43 | ``` 44 | 45 | Visually transitioning multiple elements is best done with a slight `stagger`. 46 | 47 | To enable a stagger, `` has to have access to the element's 48 | index, which is done using `:data-index="idx"` on the transitioning elements. 49 | 50 | There is a default value of `0.1s` for the stagger, which can be configured. 51 | 52 | ```vue 53 | 54 |
  • 59 | {{ item.name }} 60 |
  • 61 |
    62 | ``` 63 | 64 | ## Properties 65 | 66 | | Prop | Type | Description | 67 | | -------- | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | 68 | | hidden | `GSAPTweenVars` | State of the toggled element when hidden | 69 | | duration | `number`, default: `0.5` | The duration of the animation in seconds | 70 | | appear | `boolean`, default: `false` | Wether or not to run the enter-animation on initial load | 71 | | ease | `string`, default: `power1.inOut` | The ease of the animation. Can be overriden by the `ease` modifier in `hidden` | 72 | | delay | `number`, default: `0` | Initial delay of the transition animation in seconds | 73 | | group | `boolean`, default: `false` | Wether or not the transition applies to multiple elements. Most of the times paired with `v-if` | 74 | | stagger | `number`, default: `0.1` | Delay the appearance of each next element when using `group`. For this to work you also have to put `:data-index="idx"` on the element. | 75 | 76 | ## Full Example 77 | 78 | ```vue 79 | 86 |
    Hello World
    87 |
    88 | ``` 89 | 90 | ### Disabling default opacity animation 91 | 92 | If you want to disable the default opacity behaviour, you can set the `opacity` 93 | property in `hidden` to `1`. This way it will animate from 1 to 1 and 94 | essentially disable toggling opacity therefore. 95 | 96 | Example: 97 | 98 | ```vue 99 | 100 |
    Hello World
    101 |
    102 | ``` 103 | -------------------------------------------------------------------------------- /docs/content/2.usage/5.timeline.md: -------------------------------------------------------------------------------- 1 | # Timeline 2 | 3 | The modifier `.timeline` lets you create complex and dependent animation timelines. 4 | 5 | ## `.timeline` 6 | 7 | For a timeline to be instantiated, place `v-gsap.timeline` on the parent. You can then add animation steps on each child with `.add` 8 | 9 | ```html 10 |
    11 |

    Hello World

    12 |
    They see me animating
    13 |
    14 | ``` 15 | 16 | A timeline also supports `.whenVisible` so you can define complex entrance animations together 17 | 18 | ```html 19 |
    20 |

    Hello World

    21 |
    They see me animating
    22 |
    23 | ``` 24 | 25 | 26 | See more detailed explanation below 27 | 28 | ### `.timeline.pinned` 29 | 30 | Lets you create an animation that gets pinned once in position to then play each added steps. 31 | To define when and how long a section gets pinned, use the following properties 32 | 33 | - `start` 34 | - default `center center` 35 | - `end` 36 | - default: '+=1000px' 37 | 38 | You can set these properties individually like so: 39 | ```vue 40 |
    41 | ``` 42 | 43 | --- 44 | 45 | ### `.add` 46 | 47 | To add each individual animation step inside a timeline, use `.add` 48 | 49 | ```html 50 |
    51 |

    Hello World

    52 |

    Still here

    53 |
    54 | ``` 55 | 56 | --- 57 | 58 | #### Ordering 59 | 60 | By default, all steps are ordered like they appear in the html. 61 | 62 | ```html 63 |
    64 |

    Hello World

    65 |
    66 | They see me animating 67 | They see me animating 68 |
    69 |

    Still here

    70 |
    71 | ``` 72 | 73 | #### `.order-` 74 | 75 | If your animation steps need to be in a custom order you can use `.order` to define the order yourself: 76 | 77 | ```html 78 |
    79 |

    Hello World

    80 |
    81 | They see me animating 82 | They see me animating 83 |
    84 |

    Still here

    85 |
    86 | ``` 87 | 88 | #### `.withPrevious` 89 | 90 | If you want to run an animation step parallel to the one before, you can use this modifier. 91 | 92 | ```html 93 |
    94 |

    This runs at the

    95 |

    same time

    96 |
    97 | ``` 98 | 99 | ::alert{type="info"} 100 | For single elements with multiple steps, `.withPrevious` references the previous animation step, not the previous **element** 101 | :: 102 | 103 | --- 104 | 105 | #### Multiple Steps per Element 106 | You can also add multiple steps on one element: 107 | 108 | ```html 109 |
    110 |

    113 | Hello World 114 |

    115 |
    116 | ``` 117 | 118 | ::alert{type="warning"} 119 | You can not use the same modifier combination multiple times. A dirty hack would be to add some made-up modifier to satisfy the error. 120 | :: 121 | 122 | --- 123 | 124 | ### Callbacks 125 | 126 | There are five callbacks available that you can attach to a timeline 127 | 128 | - `.onEnter` 129 | - `.onEnterBack` 130 | - `.onLeave` 131 | - `.onLeaveBack` 132 | - `.onUpdate` 133 | 134 | All of them will pass a timeline snapshot that you can receive as prop. 135 | 136 | You can use them like so: 137 | 138 | ```vue 139 |
    140 | ... 141 |
    142 | ``` 143 | 144 | ::alert{type="info"} 145 | Due to the nature of directives, you can only use one callback per timeline. 146 | Hint: `.onUpdate` contains all information that you need deduce the other events, mainly from `direction` and `progress` 147 | :: 148 | -------------------------------------------------------------------------------- /docs/tokens.config.ts: -------------------------------------------------------------------------------- 1 | import { defineTheme } from 'pinceau' 2 | 3 | export default defineTheme({ 4 | color: { 5 | black: '#0B0A0A', 6 | // Primary is modified lightblue 7 | primary: { 8 | 50: '#effef2', 9 | 100: '#d8ffe3', 10 | 200: '#b4fec7', 11 | 300: '#7afb9d', 12 | 400: '#39ef6b', 13 | 500: '#10d747', 14 | 600: '#06b236', 15 | 700: '#098c2e', 16 | 800: '#0d6e29', 17 | 900: '#0d5a25', 18 | 950: '#003310', 19 | }, 20 | gray: { 21 | 50: '#FBFBFB', 22 | 100: '#F6F5F4', 23 | 200: '#ECEBE8', 24 | 300: '#DBD9D3', 25 | 400: '#ADA9A4', 26 | 500: '#97948F', 27 | 600: '#67635D', 28 | 700: '#36332E', 29 | 800: '#201E1B', 30 | 900: '#121110', 31 | }, 32 | red: { 33 | 50: '#FFF9F8', 34 | 100: '#FFF3F0', 35 | 200: '#FFDED7', 36 | 300: '#FFA692', 37 | 400: '#FF7353', 38 | 500: '#FF3B10', 39 | 600: '#BB2402', 40 | 700: '#701704', 41 | 800: '#340A01', 42 | 900: '#1C0301', 43 | }, 44 | blue: { 45 | 50: '#F2FAFF', 46 | 100: '#DFF3FF', 47 | 200: '#C6EAFF', 48 | 300: '#A1DDFF', 49 | 400: '#64C7FF', 50 | 500: '#1AADFF', 51 | 600: '#0069A6', 52 | 700: '#014267', 53 | 800: '#002235', 54 | 900: '#00131D', 55 | }, 56 | green: { 57 | 50: '#ECFFF7', 58 | 100: '#DEFFF1', 59 | 200: '#C3FFE6', 60 | 300: '#86FBCB', 61 | 400: '#3CEEA5', 62 | 500: '#0DD885', 63 | 600: '#00B467', 64 | 700: '#006037', 65 | 800: '#002817', 66 | 900: '#00190F', 67 | }, 68 | yellow: { 69 | 50: '#FFFCEE', 70 | 100: '#FFF6D3', 71 | 200: '#FFF0B1', 72 | 300: '#FFE372', 73 | 400: '#FFDC4E', 74 | 500: '#FBCA05', 75 | 600: '#CBA408', 76 | 700: '#614E02', 77 | 800: '#292100', 78 | 900: '#1B1500', 79 | }, 80 | shadow: { 81 | initial: '{color.gray.400}', 82 | dark: '{color.gray.800}', 83 | }, 84 | }, 85 | shadow: { 86 | 'xs': '0px 1px 2px 0px {color.shadow}', 87 | 'sm': '0px 1px 3px 0px {color.shadow}, 0px 1px 2px -1px {color.shadow}', 88 | 'md': '0px 4px 6px -1px {color.shadow}, 0px 2px 4px -2px {color.shadow}', 89 | 'lg': '0px 10px 15px -3px {color.shadow}, 0px 4px 6px -4px {color.shadow}', 90 | 'xl': '0px 20px 25px -5px {color.shadow}, 0px 8px 10px -6px {color.shadow}', 91 | '2xl': '0px 25px 50px -12px {color.shadow}', 92 | 'none': '0px 0px 0px 0px transparent', 93 | }, 94 | docus: { 95 | $schema: { 96 | title: 'All the configurable tokens from Docus.', 97 | tags: [ 98 | '@studioIcon material-symbols:docs', 99 | ], 100 | }, 101 | body: { 102 | backgroundColor: { 103 | initial: '{color.white}', 104 | dark: '{color.black}', 105 | }, 106 | color: { 107 | initial: '{color.gray.800}', 108 | dark: '{color.gray.200}', 109 | }, 110 | fontFamily: '{font.sans}', 111 | }, 112 | header: { 113 | height: '64px', 114 | logo: { 115 | height: { 116 | initial: '{space.6}', 117 | sm: '{space.7}', 118 | }, 119 | }, 120 | title: { 121 | fontSize: '{fontSize.2xl}', 122 | fontWeight: '{fontWeight.bold}', 123 | color: { 124 | static: { 125 | initial: '{color.gray.900}', 126 | dark: '{color.gray.100}', 127 | }, 128 | hover: '{color.primary.500}', 129 | }, 130 | }, 131 | }, 132 | footer: { height: { initial: '145px', sm: '100px' }, padding: '{space.4} 0' }, 133 | readableLine: '78ch', 134 | loadingBar: { 135 | height: '3px', 136 | gradientColorStop1: '#00dc82', 137 | gradientColorStop2: '#34cdfe', 138 | gradientColorStop3: '#0047e1', 139 | }, 140 | search: { 141 | backdropFilter: 'blur(24px)', 142 | input: { 143 | borderRadius: '{radii.2xs}', 144 | borderWidth: '1px', 145 | borderStyle: 'solid', 146 | borderColor: { 147 | initial: '{color.gray.200}', 148 | dark: 'transparent', 149 | }, 150 | fontSize: '{fontSize.sm}', 151 | gap: '{space.2}', 152 | padding: '{space.2} {space.4}', 153 | backgroundColor: { 154 | initial: '{color.gray.200}', 155 | dark: '{color.gray.800}', 156 | }, 157 | }, 158 | results: { 159 | window: { 160 | marginX: { 161 | initial: '0', 162 | sm: '{space.4}', 163 | }, 164 | borderRadius: { 165 | initial: 'none', 166 | sm: '{radii.xs}', 167 | }, 168 | marginTop: { 169 | initial: '0', 170 | sm: '20vh', 171 | }, 172 | maxWidth: '640px', 173 | maxHeight: { 174 | initial: '100%', 175 | sm: '320px', 176 | }, 177 | }, 178 | selected: { 179 | backgroundColor: { 180 | initial: '{color.gray.300}', 181 | dark: '{color.gray.700}', 182 | }, 183 | }, 184 | highlight: { 185 | color: 'white', 186 | backgroundColor: '{color.primary.500}', 187 | }, 188 | }, 189 | }, 190 | }, 191 | typography: { 192 | color: { 193 | primary: { 194 | 50: '{color.primary.50}', 195 | 100: '{color.primary.100}', 196 | 200: '{color.primary.200}', 197 | 300: '{color.primary.300}', 198 | 400: '{color.primary.400}', 199 | 500: '{color.primary.500}', 200 | 600: '{color.primary.600}', 201 | 700: '{color.primary.700}', 202 | 800: '{color.primary.800}', 203 | 900: '{color.primary.900}', 204 | }, 205 | secondary: { 206 | 50: '{color.gray.50}', 207 | 100: '{color.gray.100}', 208 | 200: '{color.gray.200}', 209 | 300: '{color.gray.300}', 210 | 400: '{color.gray.400}', 211 | 500: '{color.gray.500}', 212 | 600: '{color.gray.600}', 213 | 700: '{color.gray.700}', 214 | 800: '{color.gray.800}', 215 | 900: '{color.gray.900}', 216 | }, 217 | }, 218 | }, 219 | }) 220 | -------------------------------------------------------------------------------- /docs/content/2.usage/3.modifiers.md: -------------------------------------------------------------------------------- 1 | # Modifiers 2 | 3 | Discover all the different features of `v-gsap`. 4 | 5 | ## `.from`, `.to`, `.fromTo`, `.call` 6 | 7 | V-GSAP supports the above four main animation types. 8 | 9 | - `.from` animates from a given state to its current state 10 | - `.to` animates from the current state to a given to-state 11 | - `.fromTo` combines both the above. It takes an Array of two states as value 12 | - `.call` takes an arrow function as value and executes it 13 | - when used with `.whenVisible.`, it will automatically be handled like 14 | `.once.` 15 | 16 | --- 17 | 18 | ## `.whenVisible` 19 | 20 | [See in Action](/playground#whenvisible) 21 | 22 | - enables scrollTrigger. It defaults to from `top 90%` to `top 50%` and `scrub` 23 | - you can overwrite these properties in the value Object. In combination with 24 | `.fromTo`, put the overrides in the second state 25 | 26 | ### `.fromInvisible` 27 | 28 | - Adds `opacity: 0` as initial state and includes `opacity: 1` in `.to` or 29 | `.fromTo`. 30 | - This prevents the element from being visible until the directive takes action. 31 | Especially needed for above-the-fold animations 32 | - With this modifier you can create entrance animations without always repeating 33 | the opacity hassle. 34 | 35 | ### `.once` 36 | 37 | Makes the animation play only once when scrolling down, and stay in its final 38 | state even when scrolling back up. 39 | 40 | ### `.once.reversible` 41 | 42 | Similar to `.once` but will reverse the animation when scrolling back up, 43 | allowing it to play again when scrolling down. 44 | 45 | ::alert{type="info"} This is the default previous behavior of `.once`. :: 46 | 47 | ### `.markers` 48 | 49 | Adds markers for debugging. 50 | 51 | ### Custom Scroller 52 | 53 | If your content is wrapped in an artificial scroll container, use 54 | `{ [...], scroller: '' }` as a selector string inside the value object 55 | to override the default scroll container. ::alert{type="info"} Note: You can 56 | also set this globally in the `nuxt.config.ts` 57 | [See details](/installation/configuration) :: 58 | 59 | --- 60 | 61 | ## `.onState--` 62 | 63 | The `.onState-` feature allows to play certain animations only when a 64 | data-attribute has a certain value. 65 | 66 | The modifier consists of 2 or 3 parts: 67 | 68 | `.onState--` 69 | 70 | - `key`: refers to the data attribute that is used as source. Example: key 71 | `test` would refer to `data-test=""` 72 | - `value`: (optional) the target value to trigger the animation. 73 | - If left out, the desired target value is `true`. This allows more readable 74 | syntax like `.onState-open` 75 | 76 | Example: 77 | 78 | ```typescript 79 |
    82 | ``` 83 | 84 | The above example code would run the animation when the `data-index` value is 85 | `2` 86 | 87 | This feature uses `MutationObserver` in the background that updates each time 88 | the given data-attribute changes. If the current value equals the target value, 89 | the animation is played. If the values don't match, the animation is reversed. 90 | 91 | ### .inherit 92 | 93 | This is a submodifier for `.onState-` and allows the data-attribute to be on 94 | some parent. This is useful if you want to update multiple child elements based 95 | on some parent state. 96 | 97 | In the background `el.closest()` is used to search for a parent that has the 98 | matching data-attribute. 99 | 100 | Simple example: 101 | 102 | ```typescript 103 |
    104 |
    105 |
    106 | ``` 107 | 108 | --- 109 | 110 | ## `.parallax` 111 | 112 | [See in Action](/playground#parallax) 113 | 114 | - enables parallax effect for this element. Brings its own scrollTrigger, 115 | therefore doesn't need extra `.whenVisible.`. 116 | - Only works with a speed settings like: 117 | 118 | ### `.slower-` 119 | 120 | ### `.faster-` 121 | 122 | Element scrolls slower or faster than the default scroll speed. 123 | 124 | - The value is a multiplier of 10% element height. E.g. `.slower-5` scrolls the 125 | element from +-50% to +-50% height. 126 | - If the value is left out, default is `5`. Meaning: `.slower` defaults to 127 | `.slower-5` (same with faster) 128 | 129 | --- 130 | 131 | ## `.stagger` 132 | 133 | [See in Action](/playground#stagger) 134 | 135 | enables stagger for immediate children if placed on the parent. Duration can be 136 | overwritten in the value object. 137 | 138 | --- 139 | 140 | ## `.infinitely` 141 | 142 | [See in Action](/playground#infinitely) 143 | 144 | This sets the repeat of the timeline to `-1` to infinitely repeat the animation 145 | 146 | --- 147 | 148 | ## `.delay-` 149 | 150 | [See in Action](/playground#delay) 151 | 152 | - can be added to delay the action for n milliseconds like `.delay-500.` 153 | - any integer value will be accepted, like `.delay-1234.` 154 | 155 | ::callout{type="warning"} #summary Note: Only works with `whenVisible` if used 156 | with `once` 157 | 158 | #content Since `whenVisible` is specifically timed via `start` and `end`, a 159 | delay only squeezes the actual animation towards `end`. 160 | 161 | With `.whenVisible.once`, the animation is no longer "timed" but "triggered", 162 | therefore `.delay-` makes sense again :: 163 | 164 | --- 165 | 166 | ## `.whileHover` 167 | 168 | [See in Action](/playground#whilehover) 169 | 170 | lets you define a hover animation. By default, it reverses on leave. 171 | 172 | ### `.noReverse` 173 | 174 | can be used to disable the reversing on leave 175 | 176 | --- 177 | 178 | ## `.draggable` 179 | 180 | [See in Action](/playground#draggable) 181 | 182 | Allows the element to be draggable 183 | 184 | ### `.x`, `.y`, `.rotation` 185 | 186 | allows to restrict the direction of the drag. 187 | 188 | ### `.bounds` 189 | 190 | restricts the container. 191 | 192 | - If left empty (`.draggable.bounds`) it restricts the parent. 193 | - A querySelector can be passed 194 | 195 | ```ts 196 | v-gsap.draggable.bounds="'.someContainer'" 197 | ``` 198 | 199 | --- 200 | 201 | ## `.animateText.` 202 | 203 | [See in Action](/playground#animatetext) 204 | 205 | Animates Text letter by letter 206 | 207 | - It can either take a string as value like 208 | 209 | ```ts 210 | v-gsap.animateText="'Das ist ein Text'" 211 | ``` 212 | 213 | - Or if no value is given, it takes the inner textContent from the element 214 | 215 | ```ts 216 |
    Inner Text
    217 | ``` 218 | 219 | ### `.slow`, `.fast` 220 | 221 | Changes the typing speed 222 | 223 | --- 224 | 225 | ## `.magnetic` 226 | 227 | [See in Action](/playground#magnetic) 228 | 229 | Makes an element be magnetically attracted to the cursor 230 | 231 | ### `.weak`, `.weaker`, `.stronger`, `.strong` 232 | 233 | Modifies the strength of attraction. ::alert{type="info"} Order: weak <- weaker 234 | <- **default (none)** -> stronger -> strong :: 235 | 236 | --- 237 | 238 | ## `.mobile` 239 | 240 | ## `.desktop` 241 | 242 | [See in Action](/playground#desktop) 243 | 244 | With these you can specify the viewport on which the animation shall take place. 245 | 246 | - Window resizing is handled automatically. 247 | - Breakpoint is `768px` 248 | - You can combine both. Simply use both `v-gsap.mobile.` AND `v-gsap.desktop.` 249 | on the same element. 250 | 251 | --- 252 | 253 | ## `.preset` 254 | 255 | [See in Action](/playground#preset) 256 | 257 | You can define presets in `nuxt.config.ts` to make animations reusable. 258 | 259 | [See details](/usage/presets) 260 | 261 | --- 262 | 263 | ## `.entrance` 264 | 265 | This is similar to `.preset`, but focuses on predefined entrance animations. 266 | There are the following options out of the box: 267 | 268 | - `.slide-left` 269 | - Slides and fades in from left 270 | - `.slide-right` 271 | - Slides and fades in from right 272 | - `.slide-top` 273 | - Slides and fades in from top 274 | - `.slide-bottom` 275 | - Slides and fades in from bottom 276 | - `.fade` 277 | - Simply fades in 278 | - `.scale` 279 | - Fades and scales in from 75% size 280 | - `.scale-full` 281 | - Fades and scales in from 0% size 282 | 283 | ::alert{type="info"} 284 | Note: `.entrance` uses `.whenVisible.once` under the hood. 285 | 286 | `.once` is needed to ensure animations run through even if scrollTrigger start 287 | is passed (e.g. in a hero section) 288 | :: 289 | 290 | --- 291 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v1.4.4 4 | 5 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.4.3...v1.4.4) 6 | 7 | ## v1.4.3 8 | 9 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.4.2...v1.4.3) 10 | 11 | ## v1.4.2 12 | 13 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.4.1...v1.4.2) 14 | 15 | ### 🩹 Fixes 16 | 17 | - Add all missing imports from GSAPTransition ([309901d](https://github.com/holux-design/v-gsap-nuxt/commit/309901d)) 18 | 19 | ### ❤️ Contributors 20 | 21 | - Harry Yaprakov 22 | 23 | ## v1.4.1 24 | 25 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.4.0...v1.4.1) 26 | 27 | - Refactoring of 28 | - allow for multiple elements with v-for 29 | - additional properties like stagger 30 | 31 | ### ❤️ Contributors 32 | 33 | - [gluharry](https://github.com/gluharry) 34 | 35 | ## v1.3.21 36 | 37 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.20...v1.3.21) 38 | 39 | ### 🩹 Fixes 40 | 41 | - Console warning issue when using preset with value of type string ([be856da](https://github.com/holux-design/v-gsap-nuxt/commit/be856da)) 42 | 43 | ### ❤️ Contributors 44 | 45 | - Holux-design 46 | 47 | ## v1.3.20 48 | 49 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.19...v1.3.20) 50 | 51 | ### 🩹 Fixes 52 | 53 | - Update handleMouseMove in addMagnetic function to better handle different size elements ([14e7dfc](https://github.com/holux-design/v-gsap-nuxt/commit/14e7dfc)) 54 | - Fine tune magnetic strength ([1ec7b4a](https://github.com/holux-design/v-gsap-nuxt/commit/1ec7b4a)) 55 | 56 | ### ❤️ Contributors 57 | 58 | - Jules Hery 59 | 60 | ## v1.3.19 61 | 62 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.18...v1.3.19) 63 | 64 | ### 🏡 Chore 65 | 66 | - **changelog:** V1.3.18 ([842d01c](https://github.com/holux-design/v-gsap-nuxt/commit/842d01c)) 67 | 68 | ### ❤️ Contributors 69 | 70 | - Holux-design 71 | 72 | ## v1.3.18 73 | 74 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.17...v1.3.18) 75 | 76 | ### 🩹 Fixes 77 | 78 | - fix missing import for `nextTick` (PR #24 by 79 | [NekoSukuriputo](https://github.com/NekoSukuriputo)) 80 | - fix "500:insertBefore" issue with `v-gsap.timeline.pinned` on forward/backward 81 | navigation 82 | - **pinned:** Handle initialization fully from beforeMount instead of SSRProps 83 | (which is not handled on browser back) // eventListener for when timeline is 84 | ready after browser navigation 85 | ([2f8534a](https://github.com/holux-design/v-gsap-nuxt/commit/2f8534a)) 86 | 87 | ### ❤️ Contributors 88 | 89 | - Holux-design 90 | - [gluharry](https://github.com/gluharry) 91 | - [NekoSukuriputo](https://github.com/NekoSukuriputo) 92 | 93 | ## v1.3.16 94 | 95 | - add type support for `useGSAP` composable 96 | 97 | ### ❤️ Contributors 98 | 99 | - [oooFreaKooo](https://github.com/oooFreaKooo) 100 | - Holux-design 101 | 102 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.15...v1.3.16) 103 | 104 | ## v1.3.14 105 | 106 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.13...v1.3.14) 107 | 108 | ### 🩹 Fixes 109 | 110 | - **entrance:** FromInvisible issue where from opacity:0 would not persist 111 | across page nav 112 | ([30ff059](https://github.com/holux-design/v-gsap-nuxt/commit/30ff059)) 113 | 114 | ### ❤️ Contributors 115 | 116 | - Holux-design 117 | 118 | ## v1.3.13 119 | 120 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.12...v1.3.13) 121 | 122 | ### 🩹 Fixes 123 | 124 | - **console-warning:** Remove scrolltrigger attributes from value before 125 | initializing the tween 126 | ([25db20a](https://github.com/holux-design/v-gsap-nuxt/commit/25db20a)) 127 | 128 | ### ❤️ Contributors 129 | 130 | - Holux-design 131 | 132 | ## v1.3.12 133 | 134 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.11...v1.3.12) 135 | 136 | ## v1.3.11 137 | 138 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.10...v1.3.11) 139 | 140 | ### 🩹 Fixes 141 | 142 | - **GSAPTransition:** Remove "GSAPTweenVars" type -> blocked build if not found 143 | with autoimport 144 | ([1664600](https://github.com/holux-design/v-gsap-nuxt/commit/1664600)) 145 | 146 | ### ❤️ Contributors 147 | 148 | - Holux-design 149 | 150 | ## v1.3.10 151 | 152 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.9...v1.3.10) 153 | 154 | ### 🩹 Fixes 155 | 156 | - **entrance:** Use `fromTo` instead of `from` to fix reverse playing on 157 | upscroll 158 | ([abbc753](https://github.com/holux-design/v-gsap-nuxt/commit/abbc753)) 159 | 160 | ### 🏡 Chore 161 | 162 | - **changelog:** V1.3.9 163 | ([84b6387](https://github.com/holux-design/v-gsap-nuxt/commit/84b6387)) 164 | 165 | ### ❤️ Contributors 166 | 167 | - Holux-design 168 | 169 | ## v1.3.9 170 | 171 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.8...v1.3.9) 172 | 173 | - GSAPTransition: remove `visible` prop (is default element state, only animate 174 | from/to `hidden` state) 175 | 176 | ### 🏡 Chore 177 | 178 | - **changelog:** V.1.3.8 179 | ([50c9bcb](https://github.com/holux-design/v-gsap-nuxt/commit/50c9bcb)) 180 | 181 | ### ❤️ Contributors 182 | 183 | - Holux-design 184 | 185 | ## v1.3.8 186 | 187 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.7...v1.3.8) 188 | 189 | - add `ease` prop to `` 190 | - code improvements // calling done-callback after v-if animation completed 191 | 192 | ## v1.3.7 193 | 194 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.6...v1.3.7) 195 | 196 | - added `GSAPTransition` wrapper component to allow GSAP transitions on 197 | v-if/v-show 198 | 199 | ## v1.3.6 200 | 201 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.5...v1.3.6) 202 | 203 | - Nothing changed, just added keywords and had to re-release to npm 🙄 204 | 205 | ## v1.3.5 206 | 207 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.4...v1.3.5) 208 | 209 | ### 🩹 Fixes 210 | 211 | - **#12:** .entrance not refreshing on page change 212 | ([6a6ba94](https://github.com/holux-design/v-gsap-nuxt/commit/6a6ba94)) 213 | 214 | ### ❤️ Contributors 215 | 216 | - Holux-design 217 | 218 | ## v1.3.4 219 | 220 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.3...v1.3.4) 221 | 222 | ### 💅 Refactors 223 | 224 | - **fromInvisible:** Move fromInvisible handling to separate css file // 225 | fix(fromInvisible.stagger) combination 226 | ([332c246](https://github.com/holux-design/v-gsap-nuxt/commit/332c246)) 227 | 228 | ### ❤️ Contributors 229 | 230 | - Holux-design 231 | 232 | ## v1.3.3 233 | 234 | - moved composable to plugin.ts 235 | - added "Adding GSAP Plugins" to docs 236 | 237 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.2...v1.3.3) 238 | 239 | ### 🩹 Fixes 240 | 241 | - Typo in docs 242 | ([a8930ea](https://github.com/holux-design/v-gsap-nuxt/commit/a8930ea)) 243 | 244 | ### ❤️ Contributors 245 | 246 | - Corentin Hervaud ([@Curs3W4ll](http://github.com/Curs3W4ll)) 247 | 248 | ## v1.3.2 249 | 250 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.3.1...v1.3.2) 251 | 252 | ## v1.3.1 253 | 254 | - added `.onState-` functionality 255 | 256 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.2.10...v1.3.1) 257 | 258 | ### 🏡 Chore 259 | 260 | - Bump version 261 | ([d7fb32f](https://github.com/holux-design/v-gsap-nuxt/commit/d7fb32f)) 262 | 263 | ### ❤️ Contributors 264 | 265 | - Holux-design 266 | 267 | ## v1.2.2 268 | 269 | - merged [PR #6](https://github.com/holux-design/v-gsap-nuxt/pull/6) by 270 | [davidparys](https://github.com/davidparys): 271 | - `.once` reverted to "really only once" 272 | - added `.once.reversible` that replays every single entrance 273 | 274 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.2.1...v1.2.2) 275 | 276 | ### 🩹 Fixes 277 | 278 | - Lint ([c97b55d](https://github.com/holux-design/v-gsap-nuxt/commit/c97b55d)) 279 | 280 | ### ❤️ Contributors 281 | 282 | - Holux-design 283 | - [davidparys](https://github.com/davidparys) 284 | 285 | ## v1.2.1 286 | 287 | - separated plugin code from nuxt module loader to allow for Vue support + docs 288 | page "Vue only" 289 | 290 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.2.0...v1.2.1) 291 | 292 | ### 🩹 Fixes 293 | 294 | - **vue:** Add export to package.json for /vue 295 | ([21f2c36](https://github.com/holux-design/v-gsap-nuxt/commit/21f2c36)) 296 | 297 | ### ❤️ Contributors 298 | 299 | - Holux-design 300 | 301 | ## v1.2.0 302 | 303 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.9...v1.2.0) 304 | 305 | ### 🚀 Enhancements 306 | 307 | - **vue:** Separate plugin code from defineNuxtPlugin 308 | ([e36c6d2](https://github.com/holux-design/v-gsap-nuxt/commit/e36c6d2)) 309 | - **vue:** Add vue plugin function 310 | ([7632a05](https://github.com/holux-design/v-gsap-nuxt/commit/7632a05)) 311 | 312 | ### 🩹 Fixes 313 | 314 | - **vue:** Linting tips 315 | ([a03112b](https://github.com/holux-design/v-gsap-nuxt/commit/a03112b)) 316 | - **vue:** Re-init gsap context inside separated directive 317 | ([673ac26](https://github.com/holux-design/v-gsap-nuxt/commit/673ac26)) 318 | 319 | ### ❤️ Contributors 320 | 321 | - Holux-design 322 | 323 | ## v1.1.9 324 | 325 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.8...v1.1.9) 326 | 327 | ### 🩹 Fixes 328 | 329 | - Gsap context adding 330 | ([3282dc0](https://github.com/holux-design/v-gsap-nuxt/commit/3282dc0)) 331 | 332 | ### ❤️ Contributors 333 | 334 | - Holux-design 335 | 336 | ## v1.1.8 337 | 338 | - reverse `.once` on upscroll (allows for being played every time the element 339 | comes into view) 340 | 341 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.7...v1.1.8) 342 | 343 | ## v1.1.7 344 | 345 | - add `.entrance` animations 346 | - Philosophy-Page updated 347 | - added robots.txt to docs 348 | - 349 | 350 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.6...v1.1.7) 351 | 352 | ### 🩹 Fixes 353 | 354 | - Lints ([76e49dd](https://github.com/holux-design/v-gsap-nuxt/commit/76e49dd)) 355 | - Typo in docs 356 | ([bfef697](https://github.com/holux-design/v-gsap-nuxt/commit/bfef697)) 357 | 358 | ### ❤️ Contributors 359 | 360 | - Holux-design 361 | 362 | ## v1.1.6 363 | 364 | - added composable `useGSAP()` with according `nuxt.config` and docs page 365 | 366 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.5...v1.1.6) 367 | 368 | ## v1.1.5 369 | 370 | - added 'scroller' option to `nuxt.config` 371 | 372 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.4...v1.1.5) 373 | 374 | ### 💅 Refactors 375 | 376 | - Feature "scroller" // add to docs // add global config 377 | ([e923c27](https://github.com/holux-design/v-gsap-nuxt/commit/e923c27)) 378 | 379 | ### ❤️ Contributors 380 | 381 | - Holux-design 382 | 383 | ## v1.1.4 384 | 385 | - fix: `.markers` 386 | - add support for `scroller` property 387 | - Textflow example in playground 388 | - add callbacks for `.timeline` 389 | - improvement: debouncing for resize listener 390 | 391 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.3...v1.1.4) 392 | 393 | ### 🩹 Fixes 394 | 395 | - Mobile startpage breaking due to overlong code snippet 396 | ([69748e4](https://github.com/holux-design/v-gsap-nuxt/commit/69748e4)) 397 | - Lint ([82f8da8](https://github.com/holux-design/v-gsap-nuxt/commit/82f8da8)) 398 | 399 | ### ❤️ Contributors 400 | 401 | - Holux-design 402 | 403 | ## v1.1.3 404 | 405 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.2...v1.1.3) 406 | 407 | ### 🩹 Fixes 408 | 409 | - .pinned not working correctly on mobile (ios) 410 | ([7839c2e](https://github.com/holux-design/v-gsap-nuxt/commit/7839c2e)) 411 | 412 | ### ❤️ Contributors 413 | 414 | - Holux-design 415 | 416 | ## v1.1.2 417 | 418 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.1.1...v1.1.2) 419 | 420 | ## v1.1.1 421 | 422 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.9...v1.1.1) 423 | 424 | ### 🩹 Fixes 425 | 426 | - Preset ([ca43f58](https://github.com/holux-design/v-gsap-nuxt/commit/ca43f58)) 427 | - Lint errors 428 | ([2302c00](https://github.com/holux-design/v-gsap-nuxt/commit/2302c00)) 429 | - Build cmd for docs (order) 430 | ([870478a](https://github.com/holux-design/v-gsap-nuxt/commit/870478a)) 431 | - Shiki // add playground redirect 432 | ([bd6f679](https://github.com/holux-design/v-gsap-nuxt/commit/bd6f679)) 433 | - Lint ([2cb4c02](https://github.com/holux-design/v-gsap-nuxt/commit/2cb4c02)) 434 | 435 | ### 🏡 Chore 436 | 437 | - **release:** V1.0.1 438 | ([2938044](https://github.com/holux-design/v-gsap-nuxt/commit/2938044)) 439 | - **release:** V1.0.2 440 | ([62a4682](https://github.com/holux-design/v-gsap-nuxt/commit/62a4682)) 441 | 442 | ### ❤️ Contributors 443 | 444 | - Holux-design 445 | 446 | ## v1.0.2 447 | 448 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.9...v1.0.2) 449 | 450 | ### 🩹 Fixes 451 | 452 | - Preset ([ca43f58](https://github.com/holux-design/v-gsap-nuxt/commit/ca43f58)) 453 | - Lint errors 454 | ([2302c00](https://github.com/holux-design/v-gsap-nuxt/commit/2302c00)) 455 | - Build cmd for docs (order) 456 | ([870478a](https://github.com/holux-design/v-gsap-nuxt/commit/870478a)) 457 | - Shiki // add playground redirect 458 | ([bd6f679](https://github.com/holux-design/v-gsap-nuxt/commit/bd6f679)) 459 | - Lint ([2cb4c02](https://github.com/holux-design/v-gsap-nuxt/commit/2cb4c02)) 460 | 461 | ### 🏡 Chore 462 | 463 | - **release:** V1.0.1 464 | ([2938044](https://github.com/holux-design/v-gsap-nuxt/commit/2938044)) 465 | 466 | ### ❤️ Contributors 467 | 468 | - Holux-design 469 | 470 | ## v1.0.1 471 | 472 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.9...v1.0.1) 473 | 474 | ### 🩹 Fixes 475 | 476 | - Preset ([ca43f58](https://github.com/holux-design/v-gsap-nuxt/commit/ca43f58)) 477 | - Lint errors 478 | ([2302c00](https://github.com/holux-design/v-gsap-nuxt/commit/2302c00)) 479 | - Build cmd for docs (order) 480 | ([870478a](https://github.com/holux-design/v-gsap-nuxt/commit/870478a)) 481 | - Shiki // add playground redirect 482 | ([bd6f679](https://github.com/holux-design/v-gsap-nuxt/commit/bd6f679)) 483 | - Lint ([2cb4c02](https://github.com/holux-design/v-gsap-nuxt/commit/2cb4c02)) 484 | 485 | ### ❤️ Contributors 486 | 487 | - Holux-design 488 | 489 | ## v1.0.9 490 | 491 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.8...v1.0.9) 492 | 493 | ## v1.0.8 494 | 495 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.7...v1.0.8) 496 | 497 | ## v1.0.7 498 | 499 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.6...v1.0.7) 500 | 501 | ## v1.0.6 502 | 503 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.5...v1.0.6) 504 | 505 | ## v1.0.5 506 | 507 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.4...v1.0.5) 508 | 509 | ### 🩹 Fixes 510 | 511 | - Og-image 512 | ([3c38017](https://github.com/holux-design/v-gsap-nuxt/commit/3c38017)) 513 | - Lint errors 514 | ([fa4ccb7](https://github.com/holux-design/v-gsap-nuxt/commit/fa4ccb7)) 515 | 516 | ### ❤️ Contributors 517 | 518 | - Holux-design 519 | 520 | ## v1.0.4 521 | 522 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.3...v1.0.4) 523 | 524 | ## v1.0.3 525 | 526 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.2...v1.0.3) 527 | 528 | ## v1.0.2 529 | 530 | [compare changes](https://github.com/holux-design/v-gsap-nuxt/compare/v1.0.1...v1.0.2) 531 | 532 | ### 🩹 Fixes 533 | 534 | - Ref is not defined on run 535 | ([1fcb94b](https://github.com/holux-design/v-gsap-nuxt/commit/1fcb94b)) 536 | 537 | ### ❤️ Contributors 538 | 539 | - Holux-design 540 | 541 | ## v1.0.1 542 | 543 | ### 🩹 Fixes 544 | 545 | - Lint issues before initial release 546 | ([0c6e012](https://github.com/your-org/my-module/commit/0c6e012)) 547 | - Failing test due to missing prepare cmd 548 | ([3b67aa0](https://github.com/your-org/my-module/commit/3b67aa0)) 549 | 550 | ### ❤️ Contributors 551 | 552 | - Holux-design 553 | -------------------------------------------------------------------------------- /src/runtime/plugin.ts: -------------------------------------------------------------------------------- 1 | import Draggable from 'gsap/Draggable' 2 | import { ScrollTrigger, ScrollToPlugin } from 'gsap/all' 3 | import { gsap } from 'gsap' 4 | import TextPlugin from 'gsap/TextPlugin' 5 | import { nextTick } from 'vue' 6 | import { uuidv4 } from './utils/utils' 7 | import { entrancePresets } from './utils/entrance-presets' 8 | import type { Preset } from './types/Preset' 9 | 10 | gsap.registerPlugin(ScrollTrigger, ScrollToPlugin, Draggable, TextPlugin) 11 | 12 | type ANIMATION_TYPES = 'from' | 'to' | 'set' | 'fromTo' | 'call' 13 | 14 | type TIMELINE_OPTIONS = { 15 | scrollTrigger?: { 16 | trigger?: string | HTMLElement 17 | id?: string 18 | start?: string 19 | end?: string 20 | scrub?: boolean | number 21 | markers?: boolean 22 | toggleActions?: string 23 | pin?: boolean 24 | pinSpacing?: string 25 | onUpdate?: any 26 | onEnter?: any 27 | onEnterBack?: any 28 | onLeave?: any 29 | onLeaveBack?: any 30 | scroller?: any 31 | } 32 | repeat?: number 33 | } 34 | 35 | const globalTimelines = {} 36 | let observer: MutationObserver 37 | let intersectionObserver: IntersectionObserver 38 | 39 | export const vGsapDirective = ( 40 | appType: 'nuxt' | 'vue', 41 | configOptions, 42 | gsapContext, 43 | resizeListener, 44 | ) => ({ 45 | getSSRProps: (binding) => { 46 | binding = loadPreset(binding, configOptions) 47 | 48 | return { 49 | 'data-vgsap-from-invisible': binding.modifiers.fromInvisible, 50 | 'data-vgsap-stagger': binding.modifiers.stagger, 51 | } 52 | }, 53 | 54 | async beforeMount(el, binding, vnode) { 55 | binding = loadPreset(binding, configOptions) 56 | el.dataset.gsapId = uuidv4() 57 | el.dataset.vgsapFromInvisible = binding.modifiers.fromInvisible 58 | el.dataset.vgsapStagger = binding.modifiers.stagger 59 | 60 | if (!gsapContext) gsapContext = gsap.context(() => {}) 61 | 62 | if (binding.modifiers.timeline) { 63 | assignChildrenOrderAttributesFor(vnode) 64 | 65 | await nextTick() 66 | 67 | globalTimelines[el.dataset.gsapId] = prepareTimeline( 68 | el, 69 | binding, 70 | configOptions, 71 | ) 72 | el.dataset.gsapTimeline = true 73 | 74 | gsapContext.add(() => globalTimelines[el.dataset.gsapId]) 75 | } 76 | }, 77 | 78 | mounted(el, binding) { 79 | let timeline 80 | const mm = gsap.matchMedia() 81 | 82 | // Refresh scrollTrigger from .timeline after all has mounted 83 | if (binding.modifiers.timeline) { 84 | globalTimelines[el.dataset.gsapId]?.scrollTrigger?.refresh() 85 | ScrollTrigger?.normalizeScroll(true) 86 | } 87 | else { 88 | // All directives that are not .timeline 89 | 90 | if (binding.modifiers.magnetic) return addMagneticEffect(el, binding) 91 | 92 | const breakpoint = configOptions?.breakpoint || 768 93 | if (binding.modifiers.desktop) { 94 | mm.add(`(min-width: ${breakpoint}px)`, () => { 95 | timeline = prepareTimeline(el, binding, configOptions) 96 | }) 97 | } 98 | else if (binding.modifiers.mobile) { 99 | mm.add(`(max-width: ${breakpoint}px)`, () => { 100 | timeline = prepareTimeline(el, binding, configOptions) 101 | }) 102 | } 103 | else { 104 | timeline = prepareTimeline(el, binding, configOptions) 105 | } 106 | 107 | if (binding.modifiers.add) { 108 | let order 109 | = getValueFromModifier(binding, 'order-') 110 | || getValueFromModifier(binding, 'suggestedOrder-') 111 | if (binding.modifiers.withPrevious) order = '<' 112 | 113 | if (!el.closest(`[data-gsap-timeline="true"]`)?.dataset?.gsapId) return 114 | 115 | globalTimelines[ 116 | el.closest(`[data-gsap-timeline="true"]`).dataset.gsapId 117 | ]?.add(timeline, order) 118 | } 119 | } 120 | 121 | gsapContext.add(() => timeline) 122 | resizeListener = window.addEventListener('resize', () => { 123 | ScrollTrigger?.refresh(true) 124 | }) 125 | }, 126 | 127 | unmounted(el) { 128 | ScrollTrigger.getById(el.dataset.gsapId)?.kill() 129 | globalTimelines[el.dataset.gsapId]?.scrollTrigger?.kill() 130 | 131 | gsapContext.revert() // remove gsap timeline 132 | removeEventListener('resize', resizeListener) // remove resizeListener 133 | if (observer) observer.disconnect() // Disconnect onState observer (if initialized) 134 | if (intersectionObserver) intersectionObserver.disconnect() // Disconnect intersection observer (if initialized) 135 | }, 136 | }) 137 | 138 | function assignChildrenOrderAttributesFor(vnode, startOrder?): number { 139 | let order = startOrder || 0 140 | 141 | const getChildren = (vnode) => { 142 | if (vnode?.children) return Array.from(vnode?.children) 143 | if (vnode?.component?.subtree) return Array.from(vnode?.ctx?.subtree) 144 | return [] 145 | } 146 | 147 | ;(getChildren(vnode) || [])?.forEach((child: any) => { 148 | ;(child?.dirs ? Array.from(child?.dirs) : [])?.forEach((dir: any) => { 149 | if (dir.modifiers.timeline) return 150 | 151 | dir.modifiers[`suggestedOrder-${order}`] = true 152 | order++ 153 | }) 154 | order = assignChildrenOrderAttributesFor(child, order) 155 | }) 156 | return order 157 | } 158 | 159 | function prepareTimeline(el, binding, configOptions) { 160 | const timelineOptions: TIMELINE_OPTIONS = {} 161 | 162 | const callbacks = prepareCallbacks(binding) 163 | 164 | // Prepare ScrollTrigger if .whenVisible. modifier is present 165 | // You can overwrite scrollTrigger Props in the value of the directive 166 | // .once. 167 | const once = binding.modifiers.call ?? binding.modifiers.once 168 | const scroller 169 | = configOptions?.scroller 170 | || binding.value?.scroller 171 | || binding.value?.[0]?.scroller 172 | || binding.value?.[1]?.scroller 173 | || undefined 174 | const scrub 175 | = binding.value?.scrub 176 | ?? binding.value?.[1]?.scrub 177 | ?? (once == true ? false : undefined) 178 | ?? true 179 | const markers = binding.modifiers.markers 180 | if (binding.modifiers.whenVisible) { 181 | timelineOptions.scrollTrigger = { 182 | trigger: el, 183 | id: el.dataset.gsapId, 184 | start: binding.value?.start ?? 'top 90%', 185 | end: binding.value?.end ?? 'top 50%', 186 | scroller, 187 | scrub, 188 | ...callbacks, 189 | markers, 190 | toggleActions: binding.modifiers.once 191 | ? binding.modifiers.reversible 192 | ? 'play none none reverse' 193 | : 'play none none none' 194 | : undefined, 195 | } 196 | } 197 | 198 | if (binding.modifiers.pinned) { 199 | const end = binding.value?.end ?? '+=1000px' 200 | timelineOptions.scrollTrigger = { 201 | trigger: el, 202 | id: el.dataset.gsapId, 203 | start: binding.value?.start ?? 'center center', 204 | end, 205 | scroller, 206 | scrub, 207 | pin: true, 208 | pinSpacing: 'margin', 209 | ...callbacks, 210 | markers, 211 | } 212 | } 213 | 214 | if (binding.modifiers.parallax) { 215 | timelineOptions.scrollTrigger = { 216 | trigger: el, 217 | id: el.dataset.gsapId, 218 | start: `top bottom`, 219 | end: `bottom top`, 220 | scroller, 221 | scrub: true, 222 | ...callbacks, 223 | markers, 224 | } 225 | } 226 | 227 | if (!once && binding.modifiers.parallax) 228 | timelineOptions.scrollTrigger!.toggleActions = 'restart none none reverse' 229 | 230 | // .infinitely. 231 | if (binding.modifiers.infinitely) timelineOptions.repeat = -1 232 | 233 | // Set up actual timeline 234 | const timeline = gsap.timeline(timelineOptions) 235 | 236 | if (binding.modifiers.parallax) { 237 | const [parallaxType, parallaxFactor] = Object.keys(binding.modifiers)! 238 | .find(m => m.includes('slower') || m.includes('faster')) 239 | ?.split('-')! 240 | const direction = parallaxType == 'slower' ? -1 : 1 241 | timeline.fromTo( 242 | el, 243 | { yPercent: +`${10 * +(parallaxFactor || 5) * direction}` }, 244 | { 245 | yPercent: +`${10 * +(parallaxFactor || 5) * direction * -1}`, 246 | ease: 'linear', 247 | }, 248 | ) 249 | // timeline.to(el, { yPercent: +`${10 * +(parallaxFactor || 5) * direction * -1}` }) 250 | } 251 | 252 | // .delay-. modifier 253 | const delayKey = Object.keys(binding.modifiers).find(modifier => 254 | modifier.includes('delay'), 255 | ) 256 | if (delayKey) { 257 | const milliseconds = delayKey.split('-')?.[1] || 500 258 | timeline.to('body', { duration: +milliseconds / 1000 }) 259 | } 260 | 261 | // Prepare stagger if .stagger. is present 262 | // Value defaults to 0.2, but can be set in the values 263 | // .stagger. 264 | const stagger = binding.modifiers.stagger 265 | ? binding.value?.stagger ?? binding.value?.[1]?.stagger ?? '0.2' 266 | : false 267 | if (binding.modifiers.stagger) el = el.children 268 | 269 | // Remove scrollTrigger attributes from binding.value to prevent console.warings "Invalid property ... Missing plugin?" 270 | delete binding.value?.start 271 | delete binding.value?.end 272 | delete binding.value?.scrub 273 | delete binding.value?.scroller 274 | delete binding.value?.markers 275 | delete binding.value?.toggleActions 276 | 277 | // Setup actual animation step // Respects stagger if set 278 | const animationType: ANIMATION_TYPES = Object.keys(binding.modifiers).find( 279 | modifier => ['to', 'from', 'set', 'fromTo', 'call'].includes(modifier), 280 | ) as ANIMATION_TYPES 281 | if (animationType == 'to') { 282 | if (binding.modifiers.fromInvisible) 283 | binding.value.opacity = binding.value.opacity || 1 284 | timeline.to(el, { ...binding.value, stagger }) 285 | } 286 | if (animationType == 'set') timeline.set(el, { ...binding.value, stagger }) 287 | if (animationType == 'from') { 288 | timeline.from(el, { 289 | ...binding.value, 290 | stagger, 291 | opacity: 292 | binding.value.opacity ?? (binding.modifiers.fromInvisible ? 0 : 1), 293 | duration: binding.value.duration || 0.5, 294 | }) 295 | if (binding.modifiers.fromInvisible) 296 | timeline.to( 297 | el, 298 | { opacity: 1, stagger, duration: binding.value.duration || 0.5 }, 299 | '<', 300 | ) 301 | } 302 | 303 | // .fromTo= 304 | if (animationType == 'fromTo') { 305 | const values = binding.value 306 | if (binding.modifiers.stagger) values[1].stagger = stagger 307 | if (binding.modifiers.fromInvisible) { 308 | values[0].opacity = 0 309 | values[1].opacity = values[1].opacity || 1 310 | } 311 | timeline.fromTo(el, binding.value?.[0], binding.value?.[1]) 312 | } 313 | 314 | // .animateText. // .slow // .fast 315 | if (binding.modifiers.animateText) { 316 | // if text is inside element => use it as value and then empty it for animation 317 | const value 318 | = typeof binding.value === 'string' 319 | ? binding.value 320 | : binding.value?.text || el.textContent 321 | if (el.textContent) el.textContent = '' 322 | 323 | const speeds = { 324 | slow: 0.5, 325 | fast: 10, 326 | } 327 | const speed 328 | = speeds[ 329 | Object.keys(binding.modifiers).find(modifier => 330 | Object.keys(speeds).includes(modifier), 331 | ) || '' 332 | ] || 2 333 | timeline.to(el, { text: { value, speed } }) 334 | } 335 | 336 | // .whileHover. 337 | if (binding.modifiers.whileHover) { 338 | timeline.pause() 339 | el.addEventListener('mouseenter', () => timeline.play()) 340 | el.addEventListener('mouseout', () => { 341 | if (binding.modifiers.noReverse) timeline.time(0).pause() 342 | else timeline.play().reverse() 343 | }) 344 | } 345 | 346 | // .call="" 347 | if (animationType == 'call') { 348 | timeline.call(binding.value) 349 | } 350 | 351 | // .draggable. // .x // .y // .rotation // .bounds (="") 352 | if (binding.modifiers.draggable) { 353 | const type = Object.keys(binding.modifiers).find(modifier => 354 | ['x', 'y', 'rotation'].includes(modifier), 355 | ) as Draggable.DraggableType 356 | Draggable.create(el, { 357 | type, 358 | bounds: binding.value || el.parentElement, 359 | }) 360 | } 361 | 362 | if (getValueFromModifier(binding, 'onState')) { 363 | const [dataKey, targetValue = 'true']: (string | boolean | number)[] 364 | = Object.keys(binding.modifiers) 365 | .find(m => m.toLowerCase().includes('onstate')) 366 | ?.split('-') 367 | ?.slice(1)! 368 | 369 | const targetElement = binding.modifiers.inherit 370 | ? (el?.[0] || el).closest(`*[data-${dataKey}]`) 371 | : el?.[0] || el 372 | 373 | const getCurrentValue = () => targetElement.dataset[dataKey] 374 | 375 | if (getCurrentValue() != targetValue) timeline.pause() 376 | 377 | observer = new MutationObserver((mutationRecords) => { 378 | const event = mutationRecords.filter( 379 | record => record.attributeName == `data-${dataKey}`, 380 | )?.[0] 381 | if (!event) return 382 | 383 | if (getCurrentValue() == targetValue) return timeline.play() 384 | else return timeline.play().reverse() 385 | }) 386 | observer.observe(targetElement, { attributes: true }) 387 | } 388 | 389 | return timeline 390 | } 391 | 392 | type CALLBACKS = { 393 | onUpdate?: any 394 | onEnter?: any 395 | onEnterBack?: any 396 | onLeave?: any 397 | onLeaveBack?: any 398 | } 399 | 400 | function prepareCallbacks(binding): CALLBACKS { 401 | const callbacks: CALLBACKS = {} 402 | 403 | if (binding.modifiers.onUpdate) callbacks.onUpdate = binding.value 404 | if (binding.modifiers.onEnter) callbacks.onEnter = binding.value 405 | if (binding.modifiers.onEnterBack) callbacks.onEnterBack = binding.value 406 | if (binding.modifiers.onLeave) callbacks.onLeave = binding.value 407 | if (binding.modifiers.onLeaveBack) callbacks.onLeaveBack = binding.value 408 | 409 | return callbacks 410 | } 411 | 412 | function addMagneticEffect(el, binding) { 413 | const strengthModifiers = { 414 | strong: 2, 415 | stronger: 1.5, 416 | weaker: 0.75, 417 | weak: 0.5, 418 | } 419 | 420 | const handleMouseMove = (e: MouseEvent) => { 421 | if (el) { 422 | const { width, height, left, right, top, bottom } 423 | = el.getBoundingClientRect() 424 | const centerX = left + width / 2 425 | const centerY = top + height / 2 426 | const deltaX = e.clientX - centerX 427 | const deltaY = e.clientY - centerY 428 | 429 | const distanceX 430 | = left < e.clientX && right > e.clientX 431 | ? 0 432 | : Math.min(Math.abs(e.clientX - left), Math.abs(e.clientX - right)) // Horizontal distance between mouse and el 433 | const distanceY 434 | = top < e.clientY && bottom > e.clientY 435 | ? 0 436 | : Math.min(Math.abs(e.clientY - top), Math.abs(e.clientY - bottom)) // Vertical distance between mouse and el 437 | 438 | const strengthFactor 439 | = Object.entries(strengthModifiers).find( 440 | entry => binding.modifiers[entry[0]], 441 | )?.[1] || 1 442 | 443 | const distance = Math.sqrt(distanceX ** 2 + distanceY ** 2) // Distance between mouse and el 444 | const centerDistance = Math.sqrt(deltaX ** 2 + deltaY ** 2) // Distance between mouse and el's center 445 | 446 | const magneticDistanceX = width / 3 // Horizontal distance for magnetic attraction 447 | const magneticDistanceY = height / 3 // Vertical distance for magnetic attraction 448 | const attractionStrength = 0.45 * strengthFactor // Magnetic strength 449 | 450 | if (distance < magneticDistanceX && distance < magneticDistanceY) { 451 | const strength 452 | = Math.abs(1 - centerDistance / 4) 453 | / ((magneticDistanceX + magneticDistanceY) / 2) 454 | gsap.to(el, { 455 | x: deltaX * strength * attractionStrength, 456 | y: deltaY * strength * attractionStrength, 457 | duration: 0.2, 458 | }) 459 | } 460 | else { 461 | gsap.to(el, { 462 | x: 0, 463 | y: 0, 464 | duration: 0.3, 465 | }) 466 | } 467 | } 468 | } 469 | 470 | intersectionObserver = new IntersectionObserver((entries) => { 471 | entries.forEach((entry) => { 472 | if (entry.isIntersecting) { 473 | window.addEventListener('mousemove', handleMouseMove) 474 | } 475 | else { 476 | window.removeEventListener('mousemove', handleMouseMove) 477 | } 478 | }) 479 | }) 480 | 481 | intersectionObserver.observe(el) 482 | } 483 | 484 | function loadPreset(binding, configOptions) { 485 | const applyPreset = (preset: Preset, binding) => { 486 | preset.modifiers 487 | .split('.') 488 | .forEach(modifier => (binding.modifiers[modifier] = true)) 489 | if (typeof binding.value == 'string') binding.value = {} 490 | if (preset.value) { 491 | if (binding.modifiers.fromTo) { 492 | binding.value = [ 493 | preset.value[0], 494 | { ...(preset.value[1] as object), ...binding.value }, 495 | ] 496 | } 497 | else binding.value = { ...(preset.value as object), ...binding.value } 498 | } 499 | } 500 | 501 | // Load Preset if .preset. modifier is set 502 | if (binding.modifiers.preset && !!configOptions?.presets?.length) { 503 | const preset: Preset = configOptions?.presets.find( 504 | preset => preset.name == binding.value, 505 | ) 506 | if (preset) applyPreset(preset, binding) 507 | } 508 | 509 | // Load .entrance. presets 510 | if (binding.modifiers.entrance) { 511 | const preset = entrancePresets.filter((preset: Preset) => 512 | Object.keys(binding.modifiers).includes(preset.name), 513 | )?.[0] 514 | if (preset) applyPreset(preset, binding) 515 | } 516 | return binding 517 | } 518 | 519 | function resetAndKillTimeline(timeline) { 520 | timeline?.restart(false, true) 521 | timeline?.kill() 522 | return undefined 523 | } 524 | 525 | function getValueFromModifier(binding, term: string) { 526 | return Object.keys(binding.modifiers) 527 | ?.find(m => m.toLowerCase().includes(term.toLowerCase())) 528 | ?.split('-')?.[1] 529 | } 530 | 531 | export const useGSAP = (): typeof gsap => { 532 | return gsap 533 | } 534 | -------------------------------------------------------------------------------- /docs/pages/playground.vue: -------------------------------------------------------------------------------- 1 | 615 | 616 | 619 | 620 | 682 | --------------------------------------------------------------------------------