├── .husky └── pre-commit ├── examples ├── with-sveltekit │ ├── .npmrc │ ├── static │ │ └── favicon.png │ ├── vite.config.ts │ ├── .gitignore │ ├── .eslintignore │ ├── src │ │ ├── app.d.ts │ │ ├── app.html │ │ └── routes │ │ │ └── +page.svelte │ ├── tsconfig.json │ ├── .eslintrc.cjs │ ├── svelte.config.js │ ├── README.md │ └── package.json └── with-svelte-vite │ ├── .vscode │ └── extensions.json │ ├── src │ ├── vite-env.d.ts │ ├── main.ts │ ├── App.svelte │ └── assets │ │ └── svelte.svg │ ├── vite.config.ts │ ├── tsconfig.node.json │ ├── svelte.config.js │ ├── .gitignore │ ├── index.html │ ├── package.json │ ├── tsconfig.json │ ├── public │ └── vite.svg │ └── README.md ├── packages └── svelte-reveal │ ├── src │ ├── styles.css │ ├── internal │ │ ├── constants.ts │ │ ├── stores.ts │ │ ├── types │ │ │ ├── options.ts │ │ │ ├── config.ts │ │ │ ├── intersection-observer.ts │ │ │ ├── devices.ts │ │ │ ├── easing.ts │ │ │ ├── transitions.ts │ │ │ └── events.ts │ │ ├── default │ │ │ ├── config.ts │ │ │ ├── easing.ts │ │ │ └── options.ts │ │ ├── utils.ts │ │ ├── reveal.ts │ │ ├── validations.ts │ │ ├── styling │ │ │ ├── media-queries.ts │ │ │ └── generation.ts │ │ ├── DOM.ts │ │ └── API.ts │ └── index.ts │ ├── .eslintrc │ ├── .eslintignore │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ ├── CHANGELOG.md │ └── README.md ├── .vscode └── settings.json ├── .prettierrc ├── .github ├── workflows │ ├── ci.yml │ └── release.yml ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── feature_request.yml │ └── bug_report.yml ├── turbo.json ├── .gitignore ├── .prettierignore ├── package.json ├── LICENSE └── README.md /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npm run format:staged -------------------------------------------------------------------------------- /examples/with-sveltekit/.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/styles.css: -------------------------------------------------------------------------------- 1 | .sr__hide { 2 | visibility: hidden; 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["svelte.svelte-vscode"] 3 | } 4 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /examples/with-sveltekit/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DaveKeehl/svelte-reveal/HEAD/examples/with-sveltekit/static/favicon.png -------------------------------------------------------------------------------- /examples/with-svelte-vite/src/main.ts: -------------------------------------------------------------------------------- 1 | import App from './App.svelte'; 2 | 3 | const app = new App({ 4 | target: document.getElementById('app') 5 | }); 6 | 7 | export default app; 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[markdown]": { 5 | "editor.formatOnSave": false 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /examples/with-sveltekit/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { sveltekit } from '@sveltejs/kit/vite'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [sveltekit()] 6 | }); 7 | -------------------------------------------------------------------------------- /examples/with-sveltekit/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | vite.config.js.timestamp-* 10 | vite.config.ts.timestamp-* 11 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A regex matching the `rootMargin` property of the Intersection Observer. 3 | */ 4 | export const ROOT_MARGIN_REGEX = /^(-?(0|([1-9]\d*))(px|%)?\s*){0,4}$/; 5 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import { svelte } from '@sveltejs/vite-plugin-svelte'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [svelte()] 7 | }); 8 | -------------------------------------------------------------------------------- /examples/with-sveltekit/.eslintignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /build 4 | /.svelte-kit 5 | /package 6 | .env 7 | .env.* 8 | !.env.example 9 | 10 | # Ignore files for PNPM, NPM and YARN 11 | pnpm-lock.yaml 12 | package-lock.json 13 | yarn.lock 14 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "strict": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess() 7 | }; 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc.json", 3 | "useTabs": false, 4 | "singleQuote": true, 5 | "trailingComma": "none", 6 | "printWidth": 120, 7 | "plugins": ["prettier-plugin-svelte"], 8 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] 9 | } 10 | -------------------------------------------------------------------------------- /examples/with-sveltekit/src/app.d.ts: -------------------------------------------------------------------------------- 1 | // See https://kit.svelte.dev/docs/types#app 2 | // for information about these interfaces 3 | declare global { 4 | namespace App { 5 | // interface Error {} 6 | // interface Locals {} 7 | // interface PageData {} 8 | // interface PageState {} 9 | // interface Platform {} 10 | } 11 | } 12 | 13 | export {}; 14 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /packages/svelte-reveal/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/eslintrc.json", 3 | "root": true, 4 | "plugins": ["@typescript-eslint"], 5 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "turbo", "prettier"], 6 | "parser": "@typescript-eslint/parser", 7 | "parserOptions": { 8 | "ecmaVersion": "latest", 9 | "sourceType": "module" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/with-sveltekit/src/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | %sveltekit.head% 8 | 9 | 10 |
%sveltekit.body%
11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + Svelte + TS 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/stores.ts: -------------------------------------------------------------------------------- 1 | import { writable } from 'svelte/store'; 2 | 3 | /** 4 | * Svelte writable store that keeps track of the creation status of the HTML 61 | -------------------------------------------------------------------------------- /examples/with-sveltekit/src/routes/+page.svelte: -------------------------------------------------------------------------------- 1 | 14 | 15 |
16 | {#each config as element} 17 |
18 |
19 |

{element.preset} transition

20 |
21 |
22 | {/each} 23 |
24 | 25 | 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 'Feature Request' 2 | description: Suggest an idea for this project 3 | title: '[FR]: ' 4 | labels: ['feature-request'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to request this feature! 10 | - type: textarea 11 | id: problem 12 | attributes: 13 | label: Describe the problem 14 | description: Please provide a clear and concise description the problem this feature would solve. The more information you can provide here, the better. 15 | placeholder: I'm always frustrated when... 16 | validations: 17 | required: true 18 | - type: textarea 19 | id: solution 20 | attributes: 21 | label: Describe the proposed solution 22 | description: Please provide a clear and concise description of what you would like to happen. 23 | placeholder: I would like to see... 24 | validations: 25 | required: true 26 | - type: textarea 27 | id: alternatives 28 | attributes: 29 | label: Alternatives considered 30 | description: "Please provide a clear and concise description of any alternative solutions or features you've considered." 31 | - type: dropdown 32 | id: importance 33 | attributes: 34 | label: Importance 35 | description: How important is this feature to you? 36 | options: 37 | - Nice to have 38 | - Would make my life easier 39 | - I cannot use Svelte Reveal without it 40 | validations: 41 | required: true 42 | - type: textarea 43 | id: additional-context 44 | attributes: 45 | label: Additional Information 46 | description: Add any other context or screenshots about the feature request here. 47 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: Report a bug you've found 3 | title: '[Bug]: ' 4 | labels: ['bug'] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to fill out this bug report! 10 | - type: dropdown 11 | id: platform 12 | attributes: 13 | label: Type of Svelte app 14 | options: 15 | - Svelte Vite template 16 | - SvelteKit 17 | - Astro + Svelte 18 | - Other (please describe later) 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: bug-description 23 | attributes: 24 | label: Describe the bug 25 | placeholder: Bug description 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: reproduction 30 | attributes: 31 | label: Reproduction 32 | description: A link to a repository that reproduces the issue. Explaining how to reproduce is generally not enough. If no reproduction is provided within a reasonable time-frame, the issue will be closed. 33 | placeholder: Reproduction 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: system-info 38 | attributes: 39 | label: System Info 40 | description: Output of `npx envinfo --system --binaries --browsers --npmPackages` in your repository. 41 | render: Shell 42 | placeholder: System, Binaries, Browsers, NPM Packages 43 | validations: 44 | required: true 45 | - type: dropdown 46 | id: severity 47 | attributes: 48 | label: Severity 49 | options: 50 | - Annoyance 51 | - Serious, but I can work around it 52 | - Blocking 53 | validations: 54 | required: true 55 | - type: textarea 56 | id: additional-context 57 | attributes: 58 | label: Additional Information 59 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/src/assets/svelte.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/default/options.ts: -------------------------------------------------------------------------------- 1 | import type { Easing } from '@/types/easing.ts'; 2 | import type { RevealEvents } from '@/types/events.ts'; 3 | import type { IntersectionObserverConfig } from '@/types/intersection-observer.ts'; 4 | import type { RevealOptions } from '@/types/options.ts'; 5 | import type { RevealTransition } from '@/types/transitions.ts'; 6 | 7 | export const defaultIntersectionObserverConfig: IntersectionObserverConfig = { 8 | root: null, 9 | rootMargin: '0px 0px 0px 0px', 10 | threshold: 0.6 11 | }; 12 | 13 | export const presets = { 14 | fade: { 15 | preset: 'fade', 16 | opacity: 0 17 | }, 18 | slide: { 19 | preset: 'slide', 20 | opacity: 0, 21 | x: -20 22 | }, 23 | fly: { 24 | preset: 'fly', 25 | opacity: 0, 26 | y: -20 27 | }, 28 | spin: { 29 | preset: 'spin', 30 | opacity: 0, 31 | rotate: -10 32 | }, 33 | blur: { 34 | preset: 'blur', 35 | opacity: 0, 36 | blur: 2 37 | }, 38 | scale: { 39 | preset: 'scale', 40 | opacity: 0, 41 | scale: 0.8 42 | } 43 | } as const; 44 | 45 | export const defaultRevealTransition: RevealTransition = { 46 | disable: false, 47 | reset: false, 48 | duration: 800, 49 | delay: 0, 50 | x: 0, 51 | y: 0, 52 | rotate: 0, 53 | blur: 0, 54 | scale: 1, 55 | ...presets['fade'] 56 | }; 57 | 58 | export const defaultRevealEasing: Easing = 'easeInOutCubic'; 59 | 60 | export const defaultRevealEvents: RevealEvents = { 61 | onRevealStart: () => null, 62 | onRevealEnd: () => null, 63 | onResetStart: () => null, 64 | onResetEnd: () => null, 65 | onMount: () => null, 66 | onUpdate: () => null, 67 | onDestroy: () => null 68 | }; 69 | 70 | /** 71 | * The default options used by the library as fallback in case the user leaves some properties undefined. 72 | */ 73 | export const defaultOptions = { 74 | ...defaultIntersectionObserverConfig, 75 | ...defaultRevealTransition, 76 | ...defaultRevealEvents, 77 | easing: defaultRevealEasing 78 | } satisfies Required; 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](https://cdn.sanity.io/images/mbh58i22/production/1f71d5306d82ec00b28d884a1d5482b92205988a-2560x1280.png) 2 | 3 | # Svelte Reveal 4 | 5 | ![npm](https://img.shields.io/npm/v/svelte-reveal) ![npm](https://img.shields.io/npm/dw/svelte-reveal) ![GitHub](https://img.shields.io/github/license/davekeehl/svelte-reveal) 6 | 7 | Svelte Reveal is a library created with the purpose of helping [Svelte](https://svelte.dev/) users add reveal on scroll animations to their web applications in the easiest way possible. This library leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) in order to know when to trigger the animations. 8 | 9 | ## Features 10 | 11 | - ⚡️ Near zero config 12 | - 👀 Intersection Observer API 13 | - 🧩 Customizable transitions 14 | - 🔌 Extensive API 15 | - 📚 Exhaustive documentation 16 | - 🔥 100% TypeScript 17 | 18 | ## Documentation 19 | 20 | The documentation is available [here](./packages/svelte-reveal/README.md). 21 | 22 | ## Monorepo 23 | 24 | | Name | Purpose | Description | 25 | | ----------------------------------------------- | ------- | ------------------------------------------------------------ | 26 | | [svelte-reveal](./packages/svelte-reveal) | package | The Svelte action that gets published on NPM | 27 | | [with-svelte-vite](./examples/with-svelte-vite) | example | Basic example project on how to use Svelte Reveal with Svelte + Vite | 28 | | [with-sveltekit](./examples/with-sveltekit) | example | Basic example project on how to use Svelte Reveal with SvelteKit | 29 | 30 | ## Troubleshooting 31 | 32 | Feel free to [open a new issue](https://github.com/DaveKeehl/svelte-reveal/issues/new/choose) in case of any problems. 33 | 34 | ## Funding 35 | 36 | [Want to buy me a coffee?](https://ko-fi.com/davekeehl) 37 | 38 | ## Versioning 39 | 40 | This project uses [Semantic Versioning](https://semver.org/) to keep track of its version number. 41 | 42 | ## Changelog 43 | 44 | [CHANGELOG](./packages/svelte-reveal/CHANGELOG.md) 45 | 46 | ## License 47 | 48 | [MIT](./LICENSE) 49 | -------------------------------------------------------------------------------- /packages/svelte-reveal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-reveal", 3 | "version": "1.1.0", 4 | "description": "Svelte action that leverages the Intersection Observer API to trigger reveal on scroll transitions", 5 | "type": "module", 6 | "main": "dist/index.cjs", 7 | "module": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "require": "./dist/index.cjs", 12 | "import": "./dist/index.js" 13 | }, 14 | "./styles.css": "./dist/styles.css" 15 | }, 16 | "files": [ 17 | "dist/" 18 | ], 19 | "scripts": { 20 | "build": "tsup src/index.ts --clean --minify --dts --format esm,cjs && cp src/styles.css dist/styles.css", 21 | "dev": "npm run build -- --watch", 22 | "predev": "npm run clean:build", 23 | "pack": "npm pack", 24 | "lint": "eslint .", 25 | "clean:build": "rimraf dist svelte-reveal-*.tgz", 26 | "clean:deps": "rimraf .turbo node_modules package-lock.json", 27 | "clean": "run-p clean:*" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/DaveKeehl/svelte-reveal.git" 32 | }, 33 | "author": { 34 | "name": "Davide Ciulla", 35 | "email": "davide.ciulla@hotmail.com" 36 | }, 37 | "license": "MIT", 38 | "bugs": { 39 | "url": "https://github.com/DaveKeehl/svelte-reveal/issues" 40 | }, 41 | "homepage": "https://github.com/DaveKeehl/svelte-reveal#readme", 42 | "keywords": [ 43 | "svelte-reveal", 44 | "svelte", 45 | "reveal", 46 | "scroll", 47 | "intersection", 48 | "observer", 49 | "transition", 50 | "animation" 51 | ], 52 | "funding": { 53 | "type": "ko-fi", 54 | "url": "https://ko-fi.com/davekeehl" 55 | }, 56 | "devDependencies": { 57 | "@types/seedrandom": "^3.0.8", 58 | "@typescript-eslint/eslint-plugin": "^7.4.0", 59 | "@typescript-eslint/parser": "^7.4.0", 60 | "eslint": "^8.57.0", 61 | "eslint-config-prettier": "^9.1.0", 62 | "eslint-config-turbo": "^1.13.0", 63 | "npm-run-all": "^4.1.5", 64 | "prettier": "^3.2.5", 65 | "rimraf": "^5.0.5", 66 | "seedrandom": "^3.0.5", 67 | "svelte": "^4.2.12", 68 | "tsup": "^8.0.2", 69 | "typescript": "^5.4.3" 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/utils.ts: -------------------------------------------------------------------------------- 1 | import { config } from '@/default/config.ts'; 2 | import { defaultIntersectionObserverConfig, defaultOptions, presets } from '@/default/options.ts'; 3 | import { validateOptions } from '@/validations.ts'; 4 | import type { IntersectionObserverConfig } from '@/types/intersection-observer.ts'; 5 | import type { RevealConfig } from '@/types/config.ts'; 6 | import type { RevealOptions } from '@/types/options.ts'; 7 | 8 | /** 9 | * Removes trailing whitespaces, newlines and tabs from a string. 10 | * @param str The string to be cleaned. 11 | * @returns The cleaned string. 12 | */ 13 | export const cleanString = (str: string): string => 14 | str 15 | .trim() 16 | .replace(/[\n|\t]/g, '') 17 | .replace(/\s(\s+)/g, ' '); 18 | 19 | /** 20 | * Creates a clone of the global configuration used by the library. 21 | * @returns The configuration clone. 22 | */ 23 | export const cloneConfig = (): RevealConfig => structuredClone(config); 24 | 25 | /** 26 | * Creates an object containing all the options needed to configure an Intersection Observer. 27 | * @param observerConfig The Intersection Observer config. 28 | * @returns The provided Intersection Observer config, with default values applied in case of unspecified properties. 29 | */ 30 | export const createIntersectionObserverConfig = (observerConfig?: Partial) => { 31 | return { 32 | root: observerConfig?.root ?? defaultIntersectionObserverConfig.root, 33 | rootMargin: observerConfig?.rootMargin ?? defaultIntersectionObserverConfig.rootMargin, 34 | threshold: observerConfig?.threshold ?? defaultIntersectionObserverConfig.threshold 35 | }; 36 | }; 37 | 38 | /** 39 | * Merges the default options with the ones provided by the user. 40 | * @param userOptions The options provided by the user. 41 | * @returns The final merged options that can be used by the rest of the library. 42 | */ 43 | export const mergeOptions = (userOptions: RevealOptions): Required => { 44 | const cleanUserOptions = Object.fromEntries(Object.entries(userOptions).filter(([, value]) => value !== undefined)); 45 | 46 | return validateOptions({ 47 | ...defaultOptions, 48 | ...presets[userOptions?.preset ?? defaultOptions.preset], 49 | ...cleanUserOptions 50 | }); 51 | }; 52 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/reveal.ts: -------------------------------------------------------------------------------- 1 | import { config } from '@/default/config.ts'; 2 | import { defaultOptions } from '@/default/options.ts'; 3 | import { mergeOptions } from '@/utils.ts'; 4 | import { isStyleTagCreatedStore, hasPageReloadedStore } from '@/stores.ts'; 5 | import { getNodeToReveal, revealNode, createIntersectionObserver } from '@/DOM.ts'; 6 | import { getRevealClassNames, createStylesheet } from '@/styling/generation.ts'; 7 | import type { ActionReturn } from '@/types/events.ts'; 8 | import type { RevealOptions } from '@/types/options.ts'; 9 | 10 | /** 11 | * Reveals a given HTML node element on scroll. 12 | * @param node The DOM node element to apply the reveal on scroll effect to. 13 | * @param userOptions User-provided options to tweak the scroll animation behavior for `node`. 14 | * @returns The action object containing the update and destroy functions for `node`. 15 | */ 16 | export const reveal = (node: HTMLElement, userOptions: RevealOptions = defaultOptions): Partial => { 17 | const options = mergeOptions(userOptions); 18 | const { disable, onRevealStart, onMount, onUpdate, onDestroy } = options; 19 | 20 | const nodeToReveal = getNodeToReveal(node); 21 | const [transitionDeclaration, transitionProperties] = getRevealClassNames(); 22 | 23 | onMount(nodeToReveal); 24 | 25 | let hasPageReloaded = false; 26 | const unsubscribePageReloaded = hasPageReloadedStore.subscribe((val: boolean) => (hasPageReloaded = val)); 27 | 28 | const navigation = window.performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming | undefined; 29 | if (navigation?.type === 'reload') hasPageReloadedStore.set(true); 30 | if (disable || (config.once && hasPageReloaded)) return {}; 31 | 32 | let isStyleTagCreated = false; 33 | const unsubscribeStyleTagCreated = isStyleTagCreatedStore.subscribe((val: boolean) => (isStyleTagCreated = val)); 34 | 35 | if (!isStyleTagCreated) { 36 | createStylesheet(); 37 | isStyleTagCreatedStore.set(true); 38 | } 39 | 40 | onRevealStart(nodeToReveal); 41 | revealNode(nodeToReveal, transitionDeclaration, transitionProperties, options); 42 | 43 | const observer = createIntersectionObserver(nodeToReveal, options, transitionDeclaration); 44 | observer.observe(nodeToReveal); 45 | 46 | return { 47 | update() { 48 | onUpdate(nodeToReveal); 49 | }, 50 | destroy() { 51 | onDestroy(nodeToReveal); 52 | unsubscribeStyleTagCreated(); 53 | unsubscribePageReloaded(); 54 | observer.disconnect(); 55 | } 56 | }; 57 | }; 58 | -------------------------------------------------------------------------------- /examples/with-svelte-vite/README.md: -------------------------------------------------------------------------------- 1 | # Svelte + TS + Vite 2 | 3 | This template should help get you started developing with Svelte and TypeScript in Vite. 4 | 5 | ## Recommended IDE Setup 6 | 7 | [VS Code](https://code.visualstudio.com/) + [Svelte](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). 8 | 9 | ## Need an official Svelte framework? 10 | 11 | Check out [SvelteKit](https://github.com/sveltejs/kit#readme), which is also powered by Vite. Deploy anywhere with its serverless-first approach and adapt to various platforms, with out of the box support for TypeScript, SCSS, and Less, and easily-added support for mdsvex, GraphQL, PostCSS, Tailwind CSS, and more. 12 | 13 | ## Technical considerations 14 | 15 | **Why use this over SvelteKit?** 16 | 17 | - It brings its own routing solution which might not be preferable for some users. 18 | - It is first and foremost a framework that just happens to use Vite under the hood, not a Vite app. 19 | 20 | This template contains as little as possible to get started with Vite + TypeScript + Svelte, while taking into account the developer experience with regards to HMR and intellisense. It demonstrates capabilities on par with the other `create-vite` templates and is a good starting point for beginners dipping their toes into a Vite + Svelte project. 21 | 22 | Should you later need the extended capabilities and extensibility provided by SvelteKit, the template has been structured similarly to SvelteKit so that it is easy to migrate. 23 | 24 | **Why `global.d.ts` instead of `compilerOptions.types` inside `jsconfig.json` or `tsconfig.json`?** 25 | 26 | Setting `compilerOptions.types` shuts out all other types not explicitly listed in the configuration. Using triple-slash references keeps the default TypeScript setting of accepting type information from the entire workspace, while also adding `svelte` and `vite/client` type information. 27 | 28 | **Why include `.vscode/extensions.json`?** 29 | 30 | Other templates indirectly recommend extensions via the README, but this file allows VS Code to prompt the user to install the recommended extension upon opening the project. 31 | 32 | **Why enable `allowJs` in the TS template?** 33 | 34 | While `allowJs: false` would indeed prevent the use of `.js` files in the project, it does not prevent the use of JavaScript syntax in `.svelte` files. In addition, it would force `checkJs: false`, bringing the worst of both worlds: not being able to guarantee the entire codebase is TypeScript, and also having worse typechecking for the existing JavaScript. In addition, there are valid use cases in which a mixed codebase may be relevant. 35 | 36 | **Why is HMR not preserving my local component state?** 37 | 38 | HMR state preservation comes with a number of gotchas! It has been disabled by default in both `svelte-hmr` and `@sveltejs/vite-plugin-svelte` due to its often surprising behavior. You can read the details [here](https://github.com/rixo/svelte-hmr#svelte-hmr). 39 | 40 | If you have state that's important to retain within a component, consider creating an external store which would not be replaced by HMR. 41 | 42 | ```ts 43 | // store.ts 44 | // An extremely simple external store 45 | import { writable } from 'svelte/store' 46 | export default writable(0) 47 | ``` 48 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/validations.ts: -------------------------------------------------------------------------------- 1 | import { ROOT_MARGIN_REGEX } from '@/constants.ts'; 2 | import { Responsive } from '@/types/devices.ts'; 3 | import type { RevealOptions } from '@/types/options.ts'; 4 | 5 | /** 6 | * Checks whether a numeric variable is within a specific range. 7 | * @param value The property to check. 8 | * @param min The range lower limit. 9 | * @param max The range upper limit. 10 | * @returns Whether the variable is within the range. 11 | */ 12 | export const inRange = (value: number, min: number, max: number) => { 13 | return value >= min && value <= max; 14 | }; 15 | 16 | /** 17 | * Checks whether a numeric variable is positive. 18 | * @param value The property to check. 19 | * @returns Whether the variable is positive. 20 | */ 21 | export const isPositive = (value: number) => value >= 0; 22 | 23 | /** 24 | * Checks whether a numeric variable is a positive integer. 25 | * @param value The property to check. 26 | * @returns Whether the variable is a positive integer. 27 | */ 28 | export const isPositiveInteger = (value: number) => { 29 | return isPositive(value) && Number.isInteger(value); 30 | }; 31 | 32 | /** 33 | * Checks whether the breakpoints overlap. 34 | * @param responsive Object that instructs the library how to handle responsiveness for a given set of devices. 35 | * @returns Whether the breapoints overlap. 36 | */ 37 | export const hasOverlappingBreakpoints = (responsive: Responsive): boolean => { 38 | const { mobile, tablet, laptop, desktop } = responsive; 39 | 40 | return ( 41 | mobile.breakpoint > tablet.breakpoint || 42 | tablet.breakpoint > laptop.breakpoint || 43 | laptop.breakpoint > desktop.breakpoint 44 | ); 45 | }; 46 | 47 | /** 48 | * Checks whether the breakpoints are valid. 49 | * @param responsive Object that instructs the library how to handle responsiveness for a given set of devices. 50 | * @returns Whether the breakpoints are valid. 51 | */ 52 | export const hasValidBreakpoints = (responsive: Responsive): boolean => { 53 | const breakpoints = Object.values(responsive).map((device) => device.breakpoint); 54 | 55 | const doBreakpointsOverlap = hasOverlappingBreakpoints(responsive); 56 | const allBreakpointsPositive = breakpoints.every((breakpoint) => isPositiveInteger(breakpoint)); 57 | 58 | return !doBreakpointsOverlap && allBreakpointsPositive; 59 | }; 60 | 61 | /** 62 | * Checks whether the provided options are valid. 63 | * @param options The options to be checked. 64 | * @returns Whether the provided options are valid. 65 | */ 66 | export const validateOptions = (options: Required) => { 67 | const isRootMarginValid = ROOT_MARGIN_REGEX.test(options.rootMargin); 68 | const isThresholdValid = inRange(options.threshold, 0, 1); 69 | const isOpacityValid = inRange(options.opacity, 0, 1); 70 | const isDelayValid = isPositive(options.delay); 71 | const isDurationValid = isPositive(options.duration); 72 | 73 | let areOptionsValid = isRootMarginValid && isThresholdValid && isOpacityValid && isDelayValid && isDurationValid; 74 | 75 | if (options.transition === 'blur') { 76 | const isBlurValid = options.transition === 'blur' && isPositive(options.blur); 77 | areOptionsValid &&= isBlurValid; 78 | } else if (options.transition === 'scale') { 79 | const isScaleValid = isPositive(options.scale); 80 | areOptionsValid &&= isScaleValid; 81 | } 82 | 83 | if (!areOptionsValid) throw new Error('Invalid options'); 84 | return options; 85 | }; 86 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | pull_request: 5 | types: 6 | - opened 7 | - synchronize 8 | - closed 9 | branches: 10 | - main 11 | 12 | jobs: 13 | check-version: 14 | runs-on: ubuntu-latest 15 | outputs: 16 | version: ${{ steps.package-version.outputs.current-version }} 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Get current version 24 | id: package-version 25 | uses: martinbeentjes/npm-get-version-action@v1.3.1 26 | with: 27 | path: 'packages/svelte-reveal' 28 | 29 | - name: Get latest tag 30 | id: latest-tag 31 | uses: 'WyriHaximus/github-action-get-previous-tag@v1' 32 | 33 | - name: Compare versions 34 | uses: madhead/semver-utils@v4 35 | id: version 36 | with: 37 | version: ${{ steps.latest-tag.outputs.tag }} 38 | 39 | - name: Detected invalid semver upgrade 40 | if: ${{ steps.package-version.outputs.current-version != steps.version.outputs.inc-major && steps.package-version.outputs.current-version != steps.version.outputs.inc-minor && steps.package-version.outputs.current-version != steps.version.outputs.inc-patch }} 41 | uses: actions/github-script@v7 42 | with: 43 | script: | 44 | core.setFailed('Invalid semver upgrade (${{ steps.latest-tag.outputs.tag }} -> ${{ steps.package-version.outputs.current-version }})') 45 | 46 | release: 47 | runs-on: ubuntu-latest 48 | needs: check-version 49 | if: github.event.pull_request.merged == true 50 | steps: 51 | - name: Checkout code 52 | uses: actions/checkout@v4 53 | with: 54 | fetch-depth: 0 55 | 56 | - name: Setup Node.js 57 | uses: actions/setup-node@v4 58 | with: 59 | node-version: 20 60 | cache: 'npm' 61 | 62 | - name: Install dependencies 63 | run: npm ci 64 | 65 | - name: Build package 66 | run: npm run build -- --filter=svelte-reveal 67 | 68 | - name: Pack build artifacts 69 | run: npm run pack -- --filter=svelte-reveal 70 | 71 | - name: Publish to NPM 72 | id: publish 73 | uses: JS-DevTools/npm-publish@v3 74 | with: 75 | token: ${{ secrets.NPM_TOKEN }} 76 | package: ./packages/svelte-reveal/package.json 77 | strategy: upgrade 78 | 79 | - name: Extract release notes 80 | id: extract-release-notes 81 | uses: ffurrer2/extract-release-notes@v1 82 | with: 83 | changelog_file: ./packages/svelte-reveal/CHANGELOG.md 84 | 85 | - name: Create new release 86 | id: create-release 87 | uses: actions/create-release@v1 88 | env: 89 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 90 | with: 91 | tag_name: ${{ needs.check-version.outputs.version }} 92 | release_name: ${{ needs.check-version.outputs.version }} 93 | draft: false 94 | prerelease: false 95 | body: ${{ steps.extract-release-notes.outputs.release_notes }} 96 | 97 | - name: Upload release asset 98 | id: upload-release-asset 99 | uses: actions/upload-release-asset@v1 100 | env: 101 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 102 | with: 103 | upload_url: ${{ steps.create-release.outputs.upload_url }} 104 | asset_path: ./packages/svelte-reveal/svelte-reveal-${{ needs.check-version.outputs.version }}.tgz 105 | asset_name: svelte-reveal-${{ needs.check-version.outputs.version }}.tgz 106 | asset_content_type: application/gzip 107 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/styling/media-queries.ts: -------------------------------------------------------------------------------- 1 | import { cleanString } from '@/utils.ts'; 2 | import { config } from '@/default/config.ts'; 3 | import { hasValidBreakpoints } from '@/validations.ts'; 4 | import type { Device, DeviceConfig, Devices } from '@/types/devices.ts'; 5 | 6 | /** 7 | * Creates the query for a set of devices whose breakpoints are within the range defined by the `start` and `end` breakpoints. 8 | * @param devices The devices used to generate the queries from. 9 | * @param start The breakpoint that starts the range to generate the query. 10 | * @param end The breakpoint that ends the range to generate the query. 11 | * @param previousDevice The (smaller) device right before the range defined by the `start` and `end` breakpoints. In other words: previousDevice --> startDevice (@start) --> endDevice(@end). 12 | * @returns The final optimal query to target the devices found within the [`start`-`end`] breakpoints range. 13 | */ 14 | const createQuery = (devices: Devices, start: number, end: number, previousDevice?: [Device, DeviceConfig]): string => { 15 | const minBreakpoint = Math.min(...devices.map(([, settings]) => settings.breakpoint)); 16 | const maxBreakpoint = Math.max(...devices.map(([, settings]) => settings.breakpoint)); 17 | 18 | const maxQuery = `(max-width: ${end}px)`; 19 | if (previousDevice === undefined || start === minBreakpoint) return maxQuery; 20 | 21 | const minQuery = `(min-width: ${previousDevice[1].breakpoint + 1}px)`; 22 | if (end === maxBreakpoint) return minQuery; 23 | 24 | return `${minQuery} and ${maxQuery}`; 25 | }; 26 | 27 | /** 28 | * Generate a set of optimal queries, given a list of supported devices. 29 | * @param devices The devices to analyze to create the queries from. 30 | * @returns A list of optimal queries that can be combined together to create a final media query to provide responsiveness to the elements to transition. 31 | */ 32 | const getOptimalQueries = (devices: Devices): string[] => { 33 | const queries: string[] = []; 34 | let i = 0; 35 | 36 | while (i < devices.length) { 37 | const rangeStartDevice = devices[i]?.[1]; 38 | 39 | if (!rangeStartDevice || !rangeStartDevice.enabled) { 40 | i++; 41 | continue; 42 | } 43 | 44 | let j = i; 45 | let query = ''; 46 | let rangeEndDevice = rangeStartDevice; 47 | 48 | while (j < devices.length && rangeEndDevice.enabled) { 49 | const start = rangeStartDevice.breakpoint; 50 | const end = rangeEndDevice.breakpoint; 51 | const previous = devices[i - 1]; 52 | query = createQuery(devices, start, end, previous); 53 | 54 | j++; 55 | rangeEndDevice = devices[j]?.[1] || rangeEndDevice; 56 | } 57 | 58 | queries.push(query); 59 | i = j; 60 | } 61 | 62 | return queries; 63 | }; 64 | 65 | /** 66 | * Decorates a set of CSS rules with media queries. 67 | * @param styles The CSS rules to be decorated. 68 | * @param responsive Object containing all the info on how to create the media queries. 69 | * @returns The CSS ruleset decorated with the media queries generated from the analysis of the `responsive` object. 70 | */ 71 | export const addMediaQueries = (styles: string): string => { 72 | if (!hasValidBreakpoints(config.responsive)) 73 | throw new Error('Cannot create media queries due to invalid breakpoints'); 74 | 75 | const devices = Object.entries(config.responsive) as Devices; 76 | const allDevicesEnabled = devices.every(([, settings]) => settings.enabled); 77 | const allDevicesDisabled = devices.every(([, settings]) => !settings.enabled); 78 | 79 | if (allDevicesEnabled) return styles; // If styles are applied to every device, you don't need media queries 80 | if (allDevicesDisabled) return ''; // Erase the styles, since the reveal effect won't run on any device 81 | 82 | const query = getOptimalQueries(devices).join(', '); 83 | 84 | return cleanString(` 85 | @media ${query} { 86 | ${styles} 87 | } 88 | `); 89 | }; 90 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/DOM.ts: -------------------------------------------------------------------------------- 1 | import { 2 | createCssTransitionProperties, 3 | createCssTransitionDeclaration, 4 | mergeRevealStyles 5 | } from '@/styling/generation.ts'; 6 | import { cleanString, createIntersectionObserverConfig } from '@/utils.ts'; 7 | import type { RevealOptions } from '@/types/options.ts'; 8 | 9 | /** 10 | * Marks a DOM node as part of the reveal process. 11 | * @param revealNode The element to be marked. 12 | * @returns The marked DOM element. 13 | */ 14 | export const markRevealNode = (revealNode: HTMLElement): HTMLElement => { 15 | revealNode.setAttribute('data-action', 'reveal'); 16 | return revealNode; 17 | }; 18 | 19 | /** 20 | * Activates the reveal effect on the target element. 21 | * @param nodeToReveal The element to be revealed. 22 | * @param transitionPropertiesClassName The CSS class to be used to create the transition properties on the target element. 23 | * @param transitionDeclarationClassName The CSS class to be used to declare the transition on the target element. 24 | * @param options The options to be applied to the reveal effect. 25 | * @returns The element to be revealed. 26 | */ 27 | export const revealNode = ( 28 | nodeToReveal: HTMLElement, 29 | transitionPropertiesClassName: string, 30 | transitionDeclarationClassName: string, 31 | options: Required 32 | ): HTMLElement => { 33 | nodeToReveal.firstElementChild?.classList.remove('sr__hide'); 34 | markRevealNode(nodeToReveal); 35 | 36 | const cssTransitionProperties = createCssTransitionProperties({ className: transitionPropertiesClassName, options }); 37 | const cssTransitionDeclaration = createCssTransitionDeclaration({ 38 | className: transitionDeclarationClassName, 39 | duration: options.duration, 40 | delay: options.delay, 41 | easing: options.easing 42 | }); 43 | const stylesheet = document.querySelector('style[data-action="reveal"]'); 44 | 45 | /** 46 | * Since I want to have only one Svelte Reveal stylesheet for all the elements in the page, 47 | * I need to check whether a Svelte Reveal stylesheet has already been created when previous 48 | * elements have been "activated" by this library. Hence, the stylesheet content is the 49 | * concatenation of the styles of all elements on which Svelte Reveal has been activated on the page. 50 | */ 51 | if (stylesheet) { 52 | const nodeToRevealStyles = cleanString([cssTransitionProperties, cssTransitionDeclaration].join(' ')); 53 | const updatedRevealStyles = mergeRevealStyles(stylesheet.innerHTML, nodeToRevealStyles); 54 | 55 | stylesheet.innerHTML = updatedRevealStyles; 56 | nodeToReveal.classList.add(transitionPropertiesClassName, transitionDeclarationClassName); 57 | } 58 | 59 | return nodeToReveal; 60 | }; 61 | 62 | /** 63 | * Get the HTML element to be revealed. 64 | * @param node The HTML element passed by the Svelte action. 65 | * @returns The HTML element to be revealed. 66 | */ 67 | export const getNodeToReveal = (node: HTMLElement): HTMLElement => { 68 | const wrapper = document.createElement('div'); 69 | const parentElement = node.parentElement; 70 | parentElement?.insertBefore(wrapper, node); 71 | wrapper.appendChild(node); 72 | return wrapper; 73 | }; 74 | 75 | /** 76 | * Creates an Intersection Observer for the node to be revealed. 77 | * @param revealNode The HTML node to observe. 78 | * @param options The reveal options. 79 | * @param className The CSS class to add/remove from/to the target element. 80 | * @returns The newly created Intersection Observer. 81 | */ 82 | export const createIntersectionObserver = ( 83 | revealNode: HTMLElement, 84 | options: Required, 85 | className: string 86 | ): IntersectionObserver => { 87 | const { reset, duration, delay, threshold, onResetStart, onResetEnd, onRevealEnd } = options; 88 | 89 | const observerConfig = createIntersectionObserverConfig(); 90 | const sleep = duration + delay; 91 | 92 | return new IntersectionObserver((entries: IntersectionObserverEntry[], observer: IntersectionObserver) => { 93 | entries.forEach((entry) => { 94 | if (reset && !entry.isIntersecting) { 95 | onResetStart(revealNode); 96 | revealNode.classList.add(className); 97 | setTimeout(() => onResetEnd(revealNode), sleep); 98 | } else if (entry.intersectionRatio >= threshold) { 99 | setTimeout(() => onRevealEnd(revealNode), sleep); 100 | revealNode.classList.remove(className); 101 | if (!reset) observer.unobserve(revealNode); 102 | } 103 | }); 104 | }, observerConfig); 105 | }; 106 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/styling/generation.ts: -------------------------------------------------------------------------------- 1 | import seedrandom from 'seedrandom'; 2 | 3 | import { markRevealNode } from '@/DOM.ts'; 4 | import { standardEasingWeights } from '@/default/easing.ts'; 5 | import { addMediaQueries } from '@/styling/media-queries.ts'; 6 | import type { Easing, EasingWeights } from '@/types/easing.ts'; 7 | import type { RevealOptions } from '@/types/options.ts'; 8 | 9 | /** 10 | * Creates the CSS stylesheet where all the reveal styles are added to. 11 | */ 12 | export const createStylesheet = (): void => { 13 | const style = document.createElement('style'); 14 | style.setAttribute('type', 'text/css'); 15 | 16 | markRevealNode(style); 17 | 18 | const head = document.querySelector('head'); 19 | if (head !== null) head.appendChild(style); 20 | }; 21 | 22 | const createRevealClassName = (type: 'transition' | 'properties', uid: string) => { 23 | return `sr__${uid}__${type}`; 24 | }; 25 | 26 | /** 27 | * Creates the CSS classes needed to add the transitions to the target element. 28 | * @param ref A reference name that will be prefixed in the class name. 29 | * @param transition The transition name to be prefixed in the class name. 30 | * @returns A tuple with the final CSS classes in the form of: [transitionDeclaration, transitionProperties]. The transition declaration class is used to declare a transition css rule to the target element. The transition properties class is used to create the actual transition. 31 | */ 32 | export const getRevealClassNames = (): [string, string] => { 33 | const seed = document.querySelectorAll('[data-action="reveal"]').length.toString(); 34 | const uid = seedrandom(seed)().toString().slice(2); 35 | 36 | const transitionDeclaration = createRevealClassName('transition', uid); 37 | const transitionProperties = createRevealClassName('properties', uid); 38 | 39 | return [transitionDeclaration, transitionProperties]; 40 | }; 41 | 42 | /** 43 | * Get the transition properties CSS rules of a given transition. 44 | * @param transition The name of the transition. 45 | * @param options The options used by the transition. 46 | * @returns The CSS rules to be used to create the given transition. 47 | */ 48 | export const createTransitionPropertyRules = ({ 49 | opacity, 50 | x, 51 | y, 52 | rotate, 53 | scale, 54 | blur 55 | }: Required): string => { 56 | return ` 57 | opacity: ${opacity}; 58 | transform: translateX(${x}px) translateY(${y}px) rotate(${rotate}deg) scale(${scale}); 59 | filter: blur(${blur}px); 60 | `; 61 | }; 62 | 63 | /** 64 | * Generates the CSS rule for the transition declaration of the target element. 65 | * @param className - The transition declaration CSS class of the target element. 66 | * @param options - The options to be used when creating the CSS for the transition declaration. 67 | * @returns The transition declaration CSS for the target element. 68 | */ 69 | export const createCssTransitionDeclaration = ({ 70 | className, 71 | duration, 72 | delay, 73 | easing 74 | }: { 75 | className: string; 76 | duration: Required['duration']; 77 | delay: Required['delay']; 78 | easing: Easing; 79 | }) => { 80 | return ` 81 | .${className} { 82 | transition: all ${duration / 1000}s ${delay / 1000}s ${getCssEasingFunction(easing)}; 83 | } 84 | `; 85 | }; 86 | 87 | /** 88 | * Generates the CSS rules for the start of the transition of the target element. 89 | * @param className - The transition properties CSS class of the target element. 90 | * @param options - The options to be used when creating the CSS for the transition properties. 91 | * @returns The transition properties CSS for the target element. 92 | */ 93 | export const createCssTransitionProperties = ({ 94 | className, 95 | options 96 | }: { 97 | className: string; 98 | options: Required; 99 | }) => { 100 | const transitionPropertiesRules = createTransitionPropertyRules(options); 101 | 102 | return ` 103 | .${className} { 104 | ${transitionPropertiesRules} 105 | } 106 | `; 107 | }; 108 | 109 | /** 110 | * Merges any existing reveal styles with the new ones for the current DOM node that is being "activated". This process is necessary because one CSS stylesheet is shared among all the elements in the page. 111 | * @param prevRevealStyles Any existing reveal styles in the Svelte Reveal stylesheet. 112 | * @param newRevealStyles The CSS of the DOM node to be revealed. 113 | * @returns The merged CSS reveal styles to be used to update the Svelte Reveal stylesheet. 114 | */ 115 | export const mergeRevealStyles = (prevRevealStyles: string, newRevealStyles: string): string => { 116 | return [prevRevealStyles, addMediaQueries(newRevealStyles).trim()].join(' '); 117 | }; 118 | 119 | /** 120 | * Creates a valid CSS easing function. 121 | * @param easing The easing function to be applied. 122 | * @param customEasing Optional tuple to create a custom cubic-bezier easing function. 123 | * @returns A valid CSS easing function. 124 | */ 125 | export const getCssEasingFunction = (easing: Easing): string => { 126 | const createEasingFunction = (weights: EasingWeights) => `cubic-bezier(${weights.join(', ')})`; 127 | 128 | if (typeof easing !== 'string') return createEasingFunction(easing); 129 | return createEasingFunction(standardEasingWeights[easing]); 130 | }; 131 | -------------------------------------------------------------------------------- /packages/svelte-reveal/src/internal/API.ts: -------------------------------------------------------------------------------- 1 | import { config } from '@/default/config.ts'; 2 | import { defaultOptions } from '@/default/options.ts'; 3 | import { mergeOptions, createIntersectionObserverConfig, cloneConfig } from '@/utils.ts'; 4 | import { hasValidBreakpoints, inRange } from '@/validations.ts'; 5 | import { ROOT_MARGIN_REGEX } from '@/constants.ts'; 6 | import type { RevealConfig } from '@/types/config.ts'; 7 | import type { Device, DeviceConfig, Responsive } from '@/types/devices.ts'; 8 | import type { IntersectionObserverConfig } from '@/types/intersection-observer.ts'; 9 | import type { RevealOptions } from '@/types/options.ts'; 10 | 11 | /** 12 | * Sets the value of the global `once` property. 13 | * @param once Whether the reveal effect runs only once (i.e. it doesn't re-run on page reload). 14 | * @returns The config object with the updated `once` property, 15 | */ 16 | export const setOnce = (once: boolean): RevealConfig => { 17 | config.once = once; 18 | return config; 19 | }; 20 | 21 | /** 22 | * Sets the status of a device. 23 | * @param device The device to enable/disable. 24 | * @param status The new status for `device`. 25 | * @returns The config object with the updated `enabled` property. 26 | */ 27 | export const setDeviceStatus = (device: Device, status: boolean): RevealConfig => { 28 | return setDevicesStatus([device], status); 29 | }; 30 | 31 | /** 32 | * Sets the status of multiple devices. 33 | * @param devices The devices to enabled/disable. 34 | * @param status The devices' new status. 35 | * @returns The config object with the updated `enabled` properties. 36 | */ 37 | export const setDevicesStatus = (devices: Device[], status: boolean): RevealConfig => { 38 | if (devices.length === 0) throw new Error('At least one device required'); 39 | 40 | const uniqueDevices = [...new Set(devices)]; 41 | uniqueDevices.forEach((device) => (config.responsive[device].enabled = status)); 42 | return config; 43 | }; 44 | 45 | /** 46 | * Sets the breakpoint of a device. 47 | * @param device The device to update with `breakpoint`. 48 | * @param breakpoint The new breakpoint for `device`. 49 | * @returns The config object with the updated `breakpoint` property. 50 | */ 51 | export const setDeviceBreakpoint = (device: Device, breakpoint: number): RevealConfig => { 52 | const configClone = cloneConfig(); 53 | configClone.responsive[device].breakpoint = breakpoint; 54 | 55 | if (!hasValidBreakpoints(configClone.responsive)) throw new Error('Invalid breakpoints'); 56 | 57 | config.responsive[device].breakpoint = breakpoint; 58 | return config; 59 | }; 60 | 61 | /** 62 | * Sets the settings of a device. 63 | * @param device The device to update with `settings`. 64 | * @param settings The new settings for `device`. 65 | * @returns The config object with the updated device settings. 66 | */ 67 | export const setDevice = (device: Device, settings: DeviceConfig): RevealConfig => { 68 | const configClone = cloneConfig(); 69 | configClone.responsive[device] = settings; 70 | 71 | if (!hasValidBreakpoints(configClone.responsive)) throw new Error('Invalid breakpoints'); 72 | 73 | config.responsive[device] = settings; 74 | return config; 75 | }; 76 | 77 | /** 78 | * Updates how responsiveness is handled by the library. 79 | * @param responsive Object that instructs the library how to handle responsiveness. 80 | * @returns The config object with the updated `responsive` property. 81 | */ 82 | export const setResponsive = (responsive: Responsive): RevealConfig => { 83 | if (!hasValidBreakpoints(responsive)) throw new Error('Invalid breakpoints'); 84 | 85 | config.responsive = responsive; 86 | return config; 87 | }; 88 | 89 | /** 90 | * Sets the Intersection Observer `root` element. 91 | * @param root The new Intersection Observer `root` element. 92 | * @returns The Intersection Obsever configuration with the updated `root` property. 93 | */ 94 | export const setObserverRoot = (root: IntersectionObserver['root']): IntersectionObserverConfig => { 95 | defaultOptions.root = root; 96 | return createIntersectionObserverConfig(); 97 | }; 98 | 99 | /** 100 | * Sets the Intersection Observer `rootMargin` property. 101 | * @param rootMargin The new `rootMargin` used by the Intersection Observer. 102 | * @returns The Intersection Observer configuration with the updated `rootMargin` property. 103 | */ 104 | export const setObserverRootMargin = (rootMargin: IntersectionObserver['rootMargin']): IntersectionObserverConfig => { 105 | const isValidMargin = ROOT_MARGIN_REGEX.test(rootMargin); 106 | 107 | if (!isValidMargin) throw new SyntaxError('Invalid rootMargin syntax'); 108 | 109 | defaultOptions.rootMargin = rootMargin; 110 | return createIntersectionObserverConfig(); 111 | }; 112 | 113 | /** 114 | * Sets the Intersection Observer `threshold` property. 115 | * @param threshold The new `threshold` used by the Intersection Observer. 116 | * @returns The Intersection Observer configuration object with the updated `threshold` property. 117 | */ 118 | export const setObserverThreshold = (threshold: number): IntersectionObserverConfig => { 119 | if (!inRange(threshold, 0, 1)) throw new RangeError('Threshold must be between 0.0 and 1.0'); 120 | 121 | defaultOptions.threshold = threshold; 122 | return createIntersectionObserverConfig(); 123 | }; 124 | 125 | /** 126 | * Sets the Intersection Observer configuration. 127 | * @param observerConfig The new Intersection Observer configuration. 128 | * @returns The updated configuration used to manage the Intersection Observer behavior. 129 | */ 130 | export const setObserverConfig = (observerConfig: Partial): IntersectionObserverConfig => { 131 | const newObserverConfig = createIntersectionObserverConfig(observerConfig); 132 | setObserverRoot(newObserverConfig.root); 133 | setObserverRootMargin(newObserverConfig.rootMargin); 134 | setObserverThreshold(newObserverConfig.threshold); 135 | return newObserverConfig; 136 | }; 137 | 138 | /** 139 | * Updates the library global configuration. 140 | * @param userConfig The new custom configuration. 141 | * @returns The updated config object. 142 | */ 143 | export const setConfig = (userConfig: RevealConfig): RevealConfig => { 144 | setOnce(userConfig.once); 145 | setResponsive(userConfig.responsive); 146 | return config; 147 | }; 148 | 149 | /** 150 | * Updates the default options to be used for the reveal effect. 151 | * @param userOptions The new default options. 152 | * @returns The updated default options. 153 | */ 154 | export const setDefaultOptions = (userOptions: RevealOptions): Required => { 155 | return mergeOptions(userOptions); 156 | }; 157 | -------------------------------------------------------------------------------- /packages/svelte-reveal/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.1.0] - 2024-04-10 9 | 10 | ### Changed 11 | 12 | - Improved GitHub actions workflows ([#212](https://github.com/DaveKeehl/svelte-reveal/pull/212)) 13 | - Improved bundled stylesheet ([#213](https://github.com/DaveKeehl/svelte-reveal/pull/213)) 14 | - Improve bug report template ([#217](https://github.com/DaveKeehl/svelte-reveal/pull/217)) 15 | 16 | ### Fixed 17 | 18 | - Elements targeted by Svelte Reveal inside a parent node no longer appear on the bottom ([#215](https://github.com/DaveKeehl/svelte-reveal/pull/215)) 19 | 20 | ## [1.0.4] - 2024-04-05 21 | 22 | ### Fixed 23 | 24 | - Resolved release workflow misconfiguration 25 | 26 | ## [1.0.3] - 2024-04-04 27 | 28 | ### Fixed 29 | 30 | - Automatic upload of release asset via GitHub actions 31 | 32 | ## [1.0.2] - 2024-04-04 33 | 34 | ### Fixed 35 | 36 | - Automatic upload of release asset via GitHub actions 37 | 38 | ## [1.0.1] - 2024-04-04 39 | 40 | ### Fixed 41 | 42 | - GitHub actions release workflow 43 | 44 | ## [1.0.0] - 2024-04-04 45 | 46 | ### Added 47 | 48 | - Support for multiple transitions on a single element 49 | - New `preset` option, which works similarly to the now removed `transition` option 50 | 51 | ### Changed 52 | 53 | - Repository is now a monorepo containing both the published package and some example projects 54 | - Open Graph image and README cover do not contain the version number anymore 55 | - Updated dependencies 56 | - Using tsup instead of raw esbuild 57 | - Massively simplified CI/CD pipeline 58 | - Improved documentation in README 59 | - Updated JSDoc comments 60 | - Better support for SvelteKit and SSR 61 | - Restructured and simplified the core code 62 | - Reworked TypeScript types 63 | - Custom easing (array of four numbers) can now be defined directly using the `easing` option 64 | - `"easeInOutCubic"` is now the default easing function 65 | - `"blur"` option now has a default value of `0` 66 | - `"scale"` option now has a default value of `0` 67 | - `"rotate"` option now has a default value of `0` 68 | 69 | ### Removed 70 | 71 | - Dropped support for Gitpod 72 | - Dropped support for Docker 73 | - Removed testing suite 74 | - Dropped `debug`, `ref`, `highlightLogs`, `highlightColor`, `transition` and `customEasing` from the options 75 | - Dropped `dev` from the global config 76 | - Dropped `setDev` from the API 77 | - No longer adding vendor prefixes to the generated styles 78 | 79 | ### Fixed 80 | 81 | - Avoiding creating nested media queries when generating the styles 82 | - Inline styles applied to Svelte Reveal targeted elements (elements with `use:reveal`) no longer break the library ([#182](https://github.com/DaveKeehl/svelte-reveal/issues/182)) 83 | 84 | ## [0.7.0] - 2022-11-04 85 | 86 | ### Changed 87 | 88 | - Rebranded svelte-reveal to Svelte Reveal (the package name stays the same though) 89 | - Added badge for NPM weekly downloads in README 90 | - Better badges order in README 91 | - New cover image in README 92 | - Updated dependencies 93 | - Better test names 94 | - Plenty of core code refactoring to increase code quality and code readability 95 | - Updated and improved internal code documentation 96 | - `ref` option now accepts whitespaces 97 | 98 | ### Fixed 99 | 100 | - Fixed wrong options passed to the intersection observer in the `createObserver` function in DOM.ts 101 | - Fixed and improved content in README 102 | 103 | ### Removed 104 | 105 | - Removed validation steps in pre-commit hook 106 | - Options `marginTop`, `marginRight`, `marginBottom` and `marginLeft` have been deprecated in favour of `rootMargin` to better align with the Intersection Observer API 107 | 108 | ## [0.6.0] - 2022-04-22 109 | 110 | ### Added 111 | 112 | - Created unit tests for new modules 113 | 114 | ### Changed 115 | 116 | - Version numbers in CHANGELOG now allow to compare changes with previous release 117 | - Separated some business logic into separate modules 118 | - Updated README with CSS requirements for scale and spin transitions 119 | 120 | ### Removed 121 | 122 | - Vendor prefixes are not added anymore 123 | 124 | ### Fixed 125 | 126 | - Fixed and improved README (emojis have been removed from the headings to fix broken links) 127 | - Fixed some JSDoc typos 128 | 129 | ## [0.5.0] - 2022-03-15 130 | 131 | ### Changed 132 | 133 | - Documented how to use svelte-reveal with SvelteKit 134 | 135 | ## [0.4.0] - 2021-12-21 136 | 137 | ### Changed 138 | 139 | - Removed unused imports 140 | - New releases now contains output of npm pack 141 | - Sourcemap is no longer included in the bundle (package size is now drastically lower) 142 | - Added some new tests 143 | 144 | ## [0.3.3] - 2021-12-05 145 | 146 | ### Changed 147 | 148 | - Better internal architecture 149 | - General improvement to README 150 | - Extended documentation for transitions in README 151 | 152 | ### Fixed 153 | 154 | - `setDefaultOptions` now works as expected 155 | - Scale and spin transitions now look much better 156 | 157 | ## [0.3.2] - 2021-11-28 158 | 159 | ### Changed 160 | 161 | - Stopped using inline styles in favor of proper classes 162 | - Better internal project structure 163 | 164 | ### Fixed 165 | 166 | - Same type transitions now don't share properties anymore 167 | 168 | ## [0.3.1] - 2021-11-18 169 | 170 | ### Fixed 171 | 172 | - Generated tag name in CI/CD pipeline 173 | 174 | ## [0.3.0] - 2021-11-18 175 | 176 | ### Changed 177 | 178 | - Added link to a demo Svelte REPL in README 179 | 180 | ### Fixed 181 | 182 | - Fixed some broken links in README 183 | 184 | ## [0.2.0] - 2021-11-17 185 | 186 | ### Added 187 | 188 | - Bug report issue form 189 | - Feature request issue form 190 | - Docker development environment 191 | 192 | ### Changed 193 | 194 | - Improved regex responsible for cleaning strings 195 | - Better naming in GitHub CI workflow 196 | 197 | ## [0.1.0] - 2021-11-16 198 | 199 | - Initial beta release 200 | 201 | [0.1.0]: https://github.com/DaveKeehl/svelte-reveal/releases/tag/0.1.0 202 | [0.2.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.1.0...0.2.0 203 | [0.3.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.2.0...0.3.0 204 | [0.3.1]: https://github.com/DaveKeehl/svelte-reveal/compare/0.3.0...0.3.1 205 | [0.3.2]: https://github.com/DaveKeehl/svelte-reveal/compare/0.3.1...0.3.2 206 | [0.3.3]: https://github.com/DaveKeehl/svelte-reveal/compare/0.3.2...0.3.3 207 | [0.4.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.3.3...0.4.0 208 | [0.5.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.4.0...0.5.0 209 | [0.6.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.5.0...0.6.0 210 | [0.7.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.6.0...0.7.0 211 | [1.0.0]: https://github.com/DaveKeehl/svelte-reveal/compare/0.7.0...1.0.0 212 | [1.0.1]: https://github.com/DaveKeehl/svelte-reveal/compare/1.0.0...1.0.1 213 | [1.0.2]: https://github.com/DaveKeehl/svelte-reveal/compare/1.0.1...1.0.2 214 | [1.0.3]: https://github.com/DaveKeehl/svelte-reveal/compare/1.0.2...1.0.3 215 | [1.0.4]: https://github.com/DaveKeehl/svelte-reveal/compare/1.0.3...1.0.4 216 | [1.1.0]: https://github.com/DaveKeehl/svelte-reveal/compare/1.0.4...1.1.0 217 | -------------------------------------------------------------------------------- /packages/svelte-reveal/README.md: -------------------------------------------------------------------------------- 1 | ![](https://cdn.sanity.io/images/mbh58i22/production/1f71d5306d82ec00b28d884a1d5482b92205988a-2560x1280.png) 2 | 3 | # Svelte Reveal 4 | 5 | ![npm](https://img.shields.io/npm/v/svelte-reveal) ![npm](https://img.shields.io/npm/dw/svelte-reveal) ![GitHub](https://img.shields.io/github/license/davekeehl/svelte-reveal) 6 | 7 | Svelte Reveal is a library created with the purpose of helping [Svelte](https://svelte.dev/) users add reveal on scroll animations to their web applications in the easiest way possible. This library leverages the [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) in order to know when to trigger the animations. 8 | 9 | ## Features 10 | 11 | - ⚡️ Near zero config 12 | - 👀 Intersection Observer 13 | - 🧩 Customizable transitions 14 | - 🔌 Extensive API 15 | - 📚 Exhaustive documentation 16 | - 🔥 100% TypeScript 17 | 18 | ## Table of Contents 19 | 20 | 1. [Demo](#demo) 21 | 1. [Usage](#usage) 22 | 1. [Why Svelte Reveal](#why-svelte-reveal) 23 | 1. [SvelteKit](#sveltekit) 24 | 1. [Options](#options) 25 | 1. [Global config](#global-config) 26 | 1. [API](#api) 27 | 1. [Suggestions](#suggestions) 28 | 1. [Troubleshooting](#troubleshooting) 29 | 1. [Funding](#funding) 30 | 1. [Versioning](#versioning) 31 | 1. [Changelog](#changelog) 32 | 1. [License](#license) 33 | 34 | ## Demo 35 | 36 | You can see Svelte Reveal in action [in this StackBlitz project](https://stackblitz.com/edit/svelte-reveal?file=src%2FApp.svelte). 37 | 38 | ## Usage 39 | 40 | 1. Install the library: 41 | 42 | ```bash 43 | # npm 44 | npm install -D svelte-reveal 45 | 46 | # yarn 47 | yarn add -D svelte-reveal 48 | 49 | # pnpm 50 | pnpm add -D svelte-reveal 51 | 52 | # bun 53 | bun add -D svelte-reveal 54 | ``` 55 | 56 | 2. Import the library in your Svelte component: 57 | 58 | ```svelte 59 | 62 | ``` 63 | 64 | 3. Add the imported `reveal` action to the DOM element you want to transition: 65 | 66 | ```svelte 67 |

Hello world

68 |

A paragraph

69 | ``` 70 | 71 | If you want to use the action on a Svelte component, you need to pass the reveal options via props: 72 | 73 | ```svelte 74 | // App.svelte 75 | 76 | 79 | 80 | Hello world 81 | ``` 82 | 83 | ```svelte 84 | // Heading.svelte 85 | 86 | 90 | 91 |

92 | 93 |

94 | ``` 95 | 96 | Using [SvelteKit](https://kit.svelte.dev/)? Please refer to the ["SvelteKit"](#sveltekit) section. 97 | 98 | ## Why Svelte Reveal 99 | 100 | If you happened to scout the internet for other similar libraries, you might have noticed that other authors have decided to create their own library using Svelte [slots](https://svelte.dev/docs#template-syntax-slot) (similar to [React children](https://react.dev/learn/passing-props-to-a-component#passing-jsx-as-children)). There is nothing wrong with that approach, but in my opinion it goes a bit against one of Svelte's core purpose: writing more concise code. Having to wrap every to-be-transitioned component adds at least 2 extra lines of code each time, making your files unnecessarily bloated for such a simple add-on. 101 | 102 | You might have also noticed people adding event listeners to the window object in order to transition elements, but in terms of performance it [doesn't scale very well](https://itnext.io/1v1-scroll-listener-vs-intersection-observers-469a26ab9eb6). 103 | 104 | Instead, I decided to use Svelte [actions](https://svelte.dev/docs#template-syntax-element-directives-use-action), which are functions you can attach to a DOM element and that allow you to get access to that particular element and hook into its lifecycle. They take up considerably fewer lines of code, and so far I haven't encountered any big obstacle or performance drawback. Morever, this library is backed by the Intersection Observer API, which is great for performance. 105 | 106 | ## SvelteKit 107 | 108 | > ⚠️ This is an active area of research. Please [submit a bug report](https://github.com/DaveKeehl/svelte-reveal/issues/new) in case of issues. 109 | 110 | > 📅 Last update: April 2024 111 | 112 | Since Svelte actions were conceived to operate in a client-side environment, they don't always work 100% in SvelteKit and SSR (server-side rendering) out of the box. Svelte Reveal is no exception, as it needs DOM access, and in order not to incur in weird animation behaviors some small setup is required by the end-users. Out of the following two methods, pick the one that most suit your project requirements. 113 | 114 | ### Without SSR 115 | 116 | If your page doesn't need to be server-side rendered then the fix is very trivial. Turn off `ssr` in your `+page.ts` file as follows. 117 | 118 | ```typescript 119 | // +page.ts 120 | export const ssr = false; 121 | ``` 122 | 123 | ### With SSR 124 | 125 | If your page does need to leverage server-side rendering, the setup remains easy but it requires a few more steps. 126 | 127 | 1. Import the bundled stylesheet in your page or layout 128 | ```svelte 129 | // +layout.svelte 130 | 131 | 135 | 136 | ... 137 | ``` 138 | 139 | 2. Add the `sr__hide` css class to every element targeted by Svelte Reveal with `use:reveal`. This will prevent the elements to flicker as soon as the page is hydrated and the DOM is accessible to the library. 140 | ```svelte 141 | // +page.svelte 142 | 143 | 146 | 147 |

Hello world

148 | ``` 149 | 150 | ## Options 151 | 152 | Depending on the use case, you can either use this library as-is (which applies some [default options](./src/internal/default/options.ts)), or customize it to your liking. If you choose to do so, you can pass an object to this action containing your own options to be applied. 153 | 154 | Keep in mind that these options are applied to the single DOM element you add Svelte Reveal to. For global and more in-depth settings, refer to the [API](#api) section. 155 | 156 | | Name | Type | Default | Description | 157 | | ---------- | --------------------------- | ------------------- | ------------------------------------------------------------ | 158 | | `disable` | `boolean` | `false` | When set to false, the transition is disabled for the target element. | 159 | | `root` | `Element \| Document \| null` | `null` | The [root](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/root) element used by the Intersection Observer. | 160 | | `rootMargin` | `string` | `"0px 0px 0px 0px"` | The [root margin](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/rootMargin) property of the Intersection Observer. | 161 | | `threshold` | `number` | `0.6` | The [threshold](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver/thresholds) (in percentage from `0.0` to `1.0`) property used by the Intersection Observer to know when its target element is considered visible. | 162 | | `preset` | `"fade" \| "slide" \| "fly" \| "spin" \| "blur" \| "scale"` | `"fade"` | The transition preset that should be applied. Check out the ["presets"](#presets) subsection for more info. | 163 | | `reset` | `boolean` | `false` | When set to `true`, the node transitions out when out of view, and is revealed again when back in view.

⚠️ Be careful not to overuse this option, as it prevents the Intersection Observer to stop observing the target node. Performance is therefore not guaranteed when many elements have `reset` set to `true`. | 164 | | `duration` | `number` | `800` | How long the transition lasts (in ms). | 165 | | `delay` | `number` | `0` | How long the transition is delayed (in ms) before being triggered. | 166 | | `easing` | `Easing` | `"easeInOutCubic"` | The easing function to use. [Check out](./src/internal/types/easing.ts) the full list of available easing functions and [this other website](https://cubic-bezier.com/) to preview timing functions. | 167 | | `x` | `number` | `0` | The starting offset position in pixels on the x-axis. | 168 | | `y` | `number` | `0` | The starting offset position in pixels on the y-axis. | 169 | | `rotate` | `number` | `0` | The starting rotation offset in degrees. | 170 | | `opacity` | `number` | `0` | The starting opacity value. | 171 | | `blur` | `number` | `0` | The starting blur value in pixels. | 172 | | `scale` | `number` | `1` | The starting scale value in percentage. `1` corresponds to `100%`. | 173 | 174 | ### Presets 175 | 176 | > ⚠️ All presets have the `"fade"` preset backed in 177 | 178 | Presets are sets of options with predefined values, packaged under a name to achieve a certain transition effect. The following table shows the presets that come bundled with Svelte Reveal and which options they map to. 179 | 180 | | Name | Options | Description | 181 | | --------- | ----------------------------- | ------------------------------------------------------------ | 182 | | `"fade"` | `{ opacity: 0 }` | The element fades in gracefully.
In practice: `opacity: 0 -> 1` | 183 | | `"fly"` | `{ opacity: 0, y: -20 }` | The element fades in and moves along a translation on the y-axis.
In practice: `opacity: 0 -> 1` + `transform: translateY(-20px -> 0px) ` | 184 | | `"slide"` | `{ opacity: 0, x: -20 }` | The element fades in and performs a translation on the x-axis.
In practice: `opacity: 0 -> 1` + `transform: translateX(-20px -> 0px)` | 185 | | `"blur"` | `{ opacity: 0, blur: 2 }` | The element fades in and becomes unblurred.
In practice: `opacity: 0 -> 1` + `filter: blur(2px -> 0px)` | 186 | | `"scale"` | `{ opacity: 0, scale: 0.8 }` | The element fades in and gets to the original size.
In practice: `opacity: 0 -> 1` + `transform: scale(0.8 -> 1)`

⚠️ In order to use this transition it is required to use the `width` CSS property on the element to reveal. If you are not already using this property for other things, you can set it to `width: fit-content` . | 187 | | `"spin"` | `{ opacity: 0, rotate: -10 }` | The element fades in and gets to the original rotation degree.
In practice: `opacity: 0 -> 1` + `transform: rotate(-10 -> 0)`

⚠️ In order to use this transition it is required to use the `width` CSS property on the element to reveal. If you are not already using this property for other things, you can use set it to `width: fit-content` . | 188 | 189 | ### Callbacks 190 | 191 | The following table shows all the callback functions we provide to you. 192 | 193 | | Name | Args | Return | Description | 194 | | --------------- | --------------------- | ------ | ------------------------------------------------------------ | 195 | | `onRevealStart` | `(node: HTMLElement)` | `void` | Function that gets fired when the node starts being revealed. | 196 | | `onRevealEnd` | `(node: HTMLElement)` | `void` | Function that gets fired when the node is fully revealed. | 197 | | `onResetStart` | `(node: HTMLElement)` | `void` | Function that gets fired when the` reset` option is set to `true` and the node starts transitioning out. | 198 | | `onResetEnd` | `(node: HTMLElement)` | `void` | Function that gets fired when the `reset` option is set to `true` and the node has fully transitioned out. | 199 | | `onMount` | `(node: HTMLElement)` | `void` | Function that gets fired when the node is mounted on the DOM. | 200 | | `onUpdate` | `(node: HTMLElement)` | `void` | Function that gets fired when the action options are updated. | 201 | | `onDestroy` | `(node: HTMLElement)` | `void` | Function that gets fired when the node is unmounted from the DOM. | 202 | 203 | ## Global config 204 | 205 | The following table shows how this library is globally configured right of out the box. 206 | 207 | | Parameter | (children) | (children) | Type | Default | Description | 208 | | ------------ | ---------- | ------------ | -------------- | ------- | ------------------------------------------------------------ | 209 | | `once` | | | `boolean` | `false` | Whether the reveal effect runs only once (i.e. it doesn't re-run on page reload). | 210 | | `responsive` | | | `Responsive` | | Specifies how the library handles responsiveness. It can be used to enable/disable the reveal effect on some devices. | 211 | | | `mobile` | | `DeviceConfig` | | Configuration of mobile devices. | 212 | | | | `enabled` | `boolean` | `true` | Whether the reveal effect is performed on mobile devices. | 213 | | | | `breakpoint` | `number` | `425` | The max viewport width of mobile devices. | 214 | | | `tablet` | | `DeviceConfig` | | Configuration of tablet devices. | 215 | | | | `enabled` | `boolean` | `true` | Whether the reveal effect is performed on tablet devices. | 216 | | | | `breakpoint` | `number` | `768` | The max viewport width of tablet devices. | 217 | | | `laptop` | | `DeviceConfig` | | Configuration of laptop devices. | 218 | | | | `enabled` | `boolean` | `true` | Whether the reveal effect is performed on laptop devices. | 219 | | | | `breakpoint` | `number` | `1440` | The max viewport width of laptop devices. | 220 | | | `desktop` | | `DeviceConfig` | | Configuration of desktop devices. | 221 | | | | `enabled` | `boolean` | `true` | Whether the reveal effect is performed on desktop devices. | 222 | | | | `breakpoint` | `number` | `2560` | The max viewport width of desktop devices. | 223 | 224 | ## API 225 | 226 | > ⚠️ If you want to customise the behavior of a single DOM node, you are supposed to use [options](#options). 227 | 228 | Svelte Reveal also exposes several functions you can call to change the [default options](./src/internal/default/options.ts) and [global configuration](./src/internal/default/config.ts) of this library. Since these functions operate on a global level across all components using Svelte Reveal, you are supposed to only call them from a single file, otherwise you'll keep overriding the default options and global config from multiple points. 229 | 230 | | Name | Args | Return | Description | 231 | | ----------------------- | ---------------------------------------------- | ---------------------------- | ------------------------------------------------------------ | 232 | | `setOnce` | `(once: boolean)` | `RevealConfig` | Sets the reveal animations activation status on page reload. | 233 | | `setDeviceStatus` | `(device: Device, status: boolean)` | `RevealConfig` | Sets the status of a device. | 234 | | `setDevicesStatus` | `(devices: Device[], status: boolean)` | `RevealConfig` | Sets the status of multiple devices. | 235 | | `setDeviceBreakpoint` | `(device: Device, breakpoint: number)` | `RevealConfig` | Sets the breakpoint of a device. | 236 | | `setDevice` | `(device: Device, settings: IDevice)` | `RevealConfig` | Sets the settings of a device. | 237 | | `setResponsive` | `(responsive: Responsive)` | `RevealConfig` | Updates how responsiveness is handled by the library. | 238 | | `setObserverRoot` | `(root: Element \| Document \| null)` | `IntersectionObserverConfig` | Sets the Intersection Observer `root` element. | 239 | | `setObserverRootMargin` | `(rootMargin: string)` | `IntersectionObserverConfig` | Sets the Intersection Observer `rootMargin` property. | 240 | | `setObserverThreshold` | `(threshold: number)` | `IntersectionObserverConfig` | Sets the Intersection Observer `threshold` property. | 241 | | `setObserverConfig` | `(observerConfig: IntersectionObserverConfig)` | `IntersectionObserverConfig` | Sets the Intersection Observer configuration. | 242 | | `setConfig` | `(userConfig: RevealConfig)` | `RevealConfig` | Updates the library global configuration. | 243 | | `setDefaultOptions` | `(options: RevealOptions)` | `RevealOptions` | Updates the default options to be used for the reveal effect. | 244 | 245 | ## Suggestions 246 | 247 | If you need to considerably customize the behavior of this library, I suggest creating a dedicated file and to import it from the top-most component in the components tree of your project. Within that file you can then call the API functions to set global settings or shared transition properties. 248 | 249 | ```typescript 250 | // reveal.config.ts 251 | 252 | import { setDefaultOptions } from 'svelte-reveal'; 253 | 254 | setDefaultOptions({ 255 | blur: 20, 256 | x: -50, 257 | duration: 3000 258 | }); 259 | ``` 260 | 261 | ```svelte 262 | // App.svelte 263 | 264 | 267 | 268 |
{ your markup goes here }
269 | ``` 270 | 271 | ## Troubleshooting 272 | 273 | Feel free to [open a new issue](https://github.com/DaveKeehl/svelte-reveal/issues/new/choose) in case of any problems. 274 | 275 | ## Funding 276 | 277 | [Want to buy me a coffee?](https://ko-fi.com/davekeehl) 278 | 279 | ## Versioning 280 | 281 | This project uses [Semantic Versioning](https://semver.org/) to keep track of its version number. 282 | 283 | ## Changelog 284 | 285 | [CHANGELOG](./CHANGELOG.md) 286 | 287 | ## License 288 | 289 | [MIT](./LICENSE) 290 | --------------------------------------------------------------------------------