@maxigarcia/theme-transitions 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/circular-reveal/index.css +49 -0
- package/dist/circular-reveal/index.d.ts +12 -0
- package/dist/circular-reveal/index.d.ts.map +1 -0
- package/dist/circular-reveal/index.js +27 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/sweep-reveal/index.css +30 -0
- package/dist/sweep-reveal/index.d.ts +3 -0
- package/dist/sweep-reveal/index.d.ts.map +1 -0
- package/dist/sweep-reveal/index.js +29 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +8 -0
- package/package.json +50 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @maxigarcia/theme-transitions
|
|
2
|
+
|
|
3
|
+
Lightweight, CSS-first theme transitions for the web. Wrap your theme change in the [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API) and reveal the new theme with a **circular** or **sweep** animation—no animation framework required.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @maxigarcia/theme-transitions
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Animations
|
|
12
|
+
|
|
13
|
+
| Technique | Docs |
|
|
14
|
+
| --------------- | ---------------------------------------------------------------- |
|
|
15
|
+
| Circular reveal | [src/circular-reveal/README.md](./src/circular-reveal/README.md) |
|
|
16
|
+
| Sweep reveal | [src/sweep-reveal/README.md](./src/sweep-reveal/README.md) |
|
|
17
|
+
|
|
18
|
+
Each module has its own README with setup, API, and usage examples.
|
|
19
|
+
|
|
20
|
+
## Package exports
|
|
21
|
+
|
|
22
|
+
| Import | Purpose |
|
|
23
|
+
| --------------------------------------------------------- | ------------------------------------------------------------ |
|
|
24
|
+
| `@maxigarcia/theme-transitions` | `onCircularRevealAnimation`, `onSweepRevealAnimation`, types |
|
|
25
|
+
| `@maxigarcia/theme-transitions/circular-reveal/index.css` | Styles for circular reveal |
|
|
26
|
+
| `@maxigarcia/theme-transitions/sweep-reveal/index.css` | Styles for sweep reveal |
|
|
27
|
+
|
|
28
|
+
## Live demo
|
|
29
|
+
|
|
30
|
+
See the [app package](../app/) in this monorepo for an interactive landing page with both techniques.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# from monorepo root
|
|
34
|
+
npm install
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Development
|
|
39
|
+
|
|
40
|
+
From the monorepo root:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm run build -w @maxigarcia/theme-transitions
|
|
44
|
+
# or: nx run @maxigarcia/theme-transitions:build
|
|
45
|
+
npm run lint -w @maxigarcia/theme-transitions
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Versioning & publish (Nx Release)
|
|
49
|
+
|
|
50
|
+
Use [Conventional Commits](https://www.conventionalcommits.org/) for changes under this package (`feat`, `fix`, `perf`, etc.). From the monorepo root:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npm run version-packages # bump version + changelog (no publish)
|
|
54
|
+
npm run release # publish to npm
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
CI on `main` versions and tags both projects, but only publishes this package to npm. See the [root README](../../README.md#releasing).
|
|
58
|
+
|
|
59
|
+
## Browser support
|
|
60
|
+
|
|
61
|
+
Requires [View Transitions](https://caniuse.com/view-transitions) for animated reveals. Unsupported browsers still apply the theme change without animation.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
@keyframes circular-reveal {
|
|
2
|
+
from {
|
|
3
|
+
clip-path: circle(0% at var(--theme-reveal-x, 50%) var(--theme-reveal-y, 50%));
|
|
4
|
+
}
|
|
5
|
+
to {
|
|
6
|
+
clip-path: circle(150% at var(--theme-reveal-x, 50%) var(--theme-reveal-y, 50%));
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
@keyframes circular-reveal-blur {
|
|
11
|
+
from {
|
|
12
|
+
clip-path: circle(0% at var(--theme-reveal-x, 50%) var(--theme-reveal-y, 50%));
|
|
13
|
+
filter: blur(var(--theme-reveal-blur, 12px));
|
|
14
|
+
}
|
|
15
|
+
to {
|
|
16
|
+
clip-path: circle(150% at var(--theme-reveal-x, 50%) var(--theme-reveal-y, 50%));
|
|
17
|
+
filter: blur(0);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
html.circular-reveal::view-transition-old(root),
|
|
22
|
+
html.circular-reveal::view-transition-new(root) {
|
|
23
|
+
mix-blend-mode: normal;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
html.circular-reveal::view-transition-old(root) {
|
|
27
|
+
animation: none;
|
|
28
|
+
z-index: 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
html.circular-reveal::view-transition-new(root) {
|
|
32
|
+
z-index: 1;
|
|
33
|
+
animation: circular-reveal 0.5s ease-in-out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
html.circular-reveal--blur-circle::view-transition-new(root) {
|
|
37
|
+
animation: circular-reveal-blur 0.5s ease-in-out;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/* Reduce motion */
|
|
41
|
+
@media (prefers-reduced-motion: reduce) {
|
|
42
|
+
html.circular-reveal::view-transition-new(root) {
|
|
43
|
+
animation: none;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
html.circular-reveal--blur-circle::view-transition-new(root) {
|
|
47
|
+
animation: none;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ThemeRevealOrigin {
|
|
2
|
+
clientX: number;
|
|
3
|
+
clientY: number;
|
|
4
|
+
}
|
|
5
|
+
export interface CircularRevealOptions {
|
|
6
|
+
/** Blur inside the expanding circle; clears as the reveal finishes. */
|
|
7
|
+
blurCircle?: boolean;
|
|
8
|
+
/** Blur radius when `blurCircle` is true. Defaults to `12px`. */
|
|
9
|
+
blurAmount?: string;
|
|
10
|
+
}
|
|
11
|
+
export declare function onCircularRevealAnimation(apply: () => void, origin: ThemeRevealOrigin, options?: CircularRevealOptions): void;
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/circular-reveal/index.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,qBAAqB;IACpC,uEAAuE;IACvE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iEAAiE;IACjE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,MAAM,IAAI,EACjB,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,IAAI,CAmCN"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { $html, $onTransitionEnd } from '../utils/index.js';
|
|
2
|
+
export function onCircularRevealAnimation(apply, origin, options) {
|
|
3
|
+
const x = (origin.clientX / window.innerWidth) * 100;
|
|
4
|
+
const y = (origin.clientY / window.innerHeight) * 100;
|
|
5
|
+
const html = $html();
|
|
6
|
+
html.classList.add('circular-reveal');
|
|
7
|
+
if (options?.blurCircle) {
|
|
8
|
+
html.classList.add('circular-reveal--blur-circle');
|
|
9
|
+
if (options.blurAmount) {
|
|
10
|
+
html.style.setProperty('--theme-reveal-blur', options.blurAmount);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
html.style.setProperty('--theme-reveal-x', `${x}%`);
|
|
14
|
+
html.style.setProperty('--theme-reveal-y', `${y}%`);
|
|
15
|
+
if (!document.startViewTransition) {
|
|
16
|
+
apply();
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
document.startViewTransition(apply);
|
|
20
|
+
const animationComplete = () => {
|
|
21
|
+
html.style.removeProperty('--theme-reveal-x');
|
|
22
|
+
html.style.removeProperty('--theme-reveal-y');
|
|
23
|
+
html.style.removeProperty('--theme-reveal-blur');
|
|
24
|
+
html.classList.remove('circular-reveal', 'circular-reveal--blur-circle');
|
|
25
|
+
};
|
|
26
|
+
$onTransitionEnd(animationComplete, 600);
|
|
27
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAC;AAC3C,cAAc,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
@keyframes sweep-reveal {
|
|
2
|
+
from {
|
|
3
|
+
clip-path: var(--theme-reveal-direction-from);
|
|
4
|
+
}
|
|
5
|
+
to {
|
|
6
|
+
clip-path: var(--theme-reveal-direction-to);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
html.sweep-reveal::view-transition-old(root),
|
|
11
|
+
html.sweep-reveal::view-transition-new(root) {
|
|
12
|
+
animation: none;
|
|
13
|
+
mix-blend-mode: normal;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
html.sweep-reveal::view-transition-old(root) {
|
|
17
|
+
z-index: 0;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
html.sweep-reveal::view-transition-new(root) {
|
|
21
|
+
z-index: 1;
|
|
22
|
+
animation: sweep-reveal 0.45s ease-in-out forwards;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Reduce motion */
|
|
26
|
+
@media (prefers-reduced-motion: reduce) {
|
|
27
|
+
html.sweep-reveal::view-transition-new(root) {
|
|
28
|
+
animation: none;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export type SweepDirection = 'up' | 'down' | 'left' | 'right' | 'corner-top-left' | 'corner-top-right' | 'corner-bottom-left' | 'corner-bottom-right';
|
|
2
|
+
export declare function onSweepRevealAnimation(apply: () => void, direction?: SweepDirection): void;
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sweep-reveal/index.ts"],"names":[],"mappings":"AAEA,MAAM,MAAM,cAAc,GACtB,IAAI,GACF,MAAM,GACN,MAAM,GACN,OAAO,GACP,iBAAiB,GACjB,kBAAkB,GAClB,oBAAoB,GACpB,qBAAqB,CAAC;AAE5B,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,IAAI,EACjB,SAAS,GAAE,cAAqB,GAC/B,IAAI,CAkCN"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { $html, $onTransitionEnd } from '../utils/index.js';
|
|
2
|
+
export function onSweepRevealAnimation(apply, direction = 'up') {
|
|
3
|
+
if (!document.startViewTransition) {
|
|
4
|
+
apply();
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const html = $html();
|
|
8
|
+
const directions = {
|
|
9
|
+
'up': ['inset(0 0 100% 0)', 'inset(0 0 0 0)'],
|
|
10
|
+
'down': ['inset(100% 0 0 0)', 'inset(0 0 0 0)'],
|
|
11
|
+
'left': ['inset(0 100% 0 0)', 'inset(0 0 0 0)'],
|
|
12
|
+
'right': ['inset(0 0 0 100%)', 'inset(0 0 0 0)'],
|
|
13
|
+
'corner-top-left': ['inset(0 100% 100% 0)', 'inset(0 0 0 0)'],
|
|
14
|
+
'corner-top-right': ['inset(0 0 100% 100%)', 'inset(0 0 0 0)'],
|
|
15
|
+
'corner-bottom-left': ['inset(100% 100% 0 0)', 'inset(0 0 0 0)'],
|
|
16
|
+
'corner-bottom-right': ['inset(100% 0 0 100%)', 'inset(0 0 0 0)'],
|
|
17
|
+
};
|
|
18
|
+
const [directionFrom, directionTo] = directions[direction] ?? directions.up;
|
|
19
|
+
html.classList.add('sweep-reveal');
|
|
20
|
+
html.style.setProperty('--theme-reveal-direction-from', directionFrom);
|
|
21
|
+
html.style.setProperty('--theme-reveal-direction-to', directionTo);
|
|
22
|
+
document.startViewTransition(apply);
|
|
23
|
+
const animationComplete = () => {
|
|
24
|
+
html.classList.remove('sweep-reveal');
|
|
25
|
+
html.style.removeProperty('--theme-reveal-direction-from');
|
|
26
|
+
html.style.removeProperty('--theme-reveal-direction-to');
|
|
27
|
+
};
|
|
28
|
+
$onTransitionEnd(animationComplete, 600);
|
|
29
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,gBAEpB;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,IAAI,EAAE,QAAQ,GAAE,MAAY,QAI5E"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@maxigarcia/theme-transitions",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.2.0",
|
|
5
|
+
"license": "ISC",
|
|
6
|
+
"homepage": "https://github.com/MaxiGarcia13/js-theme-animation-monorepo/tree/main/packages/theme-transitions#readme",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/MaxiGarcia13/js-theme-animation-monorepo.git"
|
|
10
|
+
},
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/MaxiGarcia13/js-theme-animation-monorepo/issues"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"theme",
|
|
16
|
+
"transition",
|
|
17
|
+
"view transitions",
|
|
18
|
+
"view transitions api",
|
|
19
|
+
"utility",
|
|
20
|
+
"library",
|
|
21
|
+
"css",
|
|
22
|
+
"javascript",
|
|
23
|
+
"web",
|
|
24
|
+
"dark mode",
|
|
25
|
+
"light mode",
|
|
26
|
+
"dark theme",
|
|
27
|
+
"light theme"
|
|
28
|
+
],
|
|
29
|
+
"exports": {
|
|
30
|
+
".": {
|
|
31
|
+
"types": "./dist/index.d.ts",
|
|
32
|
+
"import": "./dist/index.js"
|
|
33
|
+
},
|
|
34
|
+
"./circular-reveal/index.css": "./dist/circular-reveal/index.css",
|
|
35
|
+
"./sweep-reveal/index.css": "./dist/sweep-reveal/index.css"
|
|
36
|
+
},
|
|
37
|
+
"main": "./dist/index.js",
|
|
38
|
+
"types": "./dist/index.d.ts",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist"
|
|
41
|
+
],
|
|
42
|
+
"scripts": {
|
|
43
|
+
"clean": "rm -rf dist",
|
|
44
|
+
"prebuild": "npm run clean",
|
|
45
|
+
"build": "tsc && node scripts/copy-css-files.ts",
|
|
46
|
+
"lint": "eslint .",
|
|
47
|
+
"lint:fix": "eslint --fix .",
|
|
48
|
+
"phoenix": "npm run clean && rm -rf node_modules"
|
|
49
|
+
}
|
|
50
|
+
}
|