Skip to content

Commit 24b1869

Browse files
feat: add IonAnimation component (#40)
* chore: fix typo in a comment * feat: add CreateAnimation component * chore(playground): add CreateAnimation as example * refactor: change animation ref to const Co-authored-by: Daniel Roe <[email protected]> * refactor: utilize `@nuxt/kit` for importing component * chore: ignore annoying non-null assertion eslint warning * refactor: rename component to `IonAnimation` * chore: update playground animations example * feat: add `playOnVisible` prop * feat: add keyframes * chore: update animation examples * feat: add props for animation hooks * style: remove comment * docs: add utility component to features * chore: update dependencies * refactor: remove div wrapper for animation component * chore(playground): add more animation examples * docs: document animation component * chore: update deps Co-authored-by: Daniel Roe <[email protected]>
1 parent 50fda75 commit 24b1869

File tree

11 files changed

+729
-323
lines changed

11 files changed

+729
-323
lines changed

docs/content/3.features.md

Lines changed: 58 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,70 @@ definePageMeta({
2424

2525
::
2626

27-
## Components
27+
## Ionic Framework components
2828

29-
All Ionic Vue components should be auto-imported throughout your app. (If you find one that isn't, please open an issue.) Although your IDE should be aware of these everywhere, they are not globally registered and are only imported within the components that use them.
29+
All Ionic Vue components should be auto-imported throughout your app. (If you find one that isn't, please open an issue.) Although your IDE should be aware of these everywhere, they are not globally registered and are only imported within the components that use them. For more on how component imports work, see the [Nuxt documentation](https://v3.nuxtjs.org/guide/directory-structure/components#components-directory).
3030

31-
For more on how component imports work, see the [Nuxt documentation](https://v3.nuxtjs.org/guide/directory-structure/components#components-directory).
31+
## Module utility components
32+
33+
Module also offers few components made for easier and more seamless way to integrate Ionic's composables or functions into your Nuxt app.
34+
35+
### `IonAnimation` component
36+
37+
Component made specifically to make usage of Ionic's `createAnimation` easier. It has almost 1 to 1 props matching as your usual animation object. For more see [official Ionic docs](https://ionicframework.com/docs/utilities/animations) or check out [playground examples](https://github.com/danielroe/nuxt-ionic/blob/main/playground/pages/tabs/tab4.vue)
38+
39+
Instead of using the function manually:
40+
41+
```vue
42+
<script setup lang="ts">
43+
// Template ref of your element
44+
const squareRef = ref()
45+
46+
// Your animation object
47+
const animation = createAnimation()
48+
.addElement(squareRef.value)
49+
.duration(3000)
50+
.iterations(Infinity)
51+
.keyframes([
52+
{ offset: 0, background: 'red' },
53+
{ offset: 0.72, background: 'var(--background)' },
54+
{ offset: 1, background: 'green' },
55+
])
56+
57+
onMounted(() => {
58+
animation.play()
59+
})
60+
</script>
61+
```
62+
63+
You can delegate all that logic to the component:
64+
65+
```vue
66+
<template>
67+
<IonAnimation
68+
:duration="3000"
69+
:iterations="Infinity"
70+
:keyframes="[
71+
{ offset: 0, background: 'red' },
72+
{ offset: 0.72, background: 'var(--background)' },
73+
{ offset: 1, background: 'green' },
74+
]"
75+
playOnMount
76+
>
77+
<!-- Content to animate -->
78+
</IonAnimation>
79+
</template>
80+
```
81+
82+
::alert{type=info}
83+
Currently component doesn't support grouped and chained animations. For that usage we still recommend using `createAnimation` by itself
84+
::
3285

3386
## Icons
3487

3588
Icons are auto-imported from `ionicons/icons` by default, following the pattern of camel case naming with `ionicons` in front of the original icon name from the [official icons website](https://ionic.io/ionicons).
3689

37-
For example, instead of this
90+
For example, instead of this:
3891

3992
```vue [component.vue]
4093
<script setup lang="ts">
@@ -47,7 +100,7 @@ import { image, squareSharp, triangleOutline } from 'ionicons/icons'
47100
</template>
48101
```
49102

50-
You would write this
103+
You would write this:
51104

52105
```vue [component.vue]
53106
<template>

package.json

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,16 @@
5353
"postpublish": "pinst --enable"
5454
},
5555
"dependencies": {
56-
"@capacitor/cli": "^4.0.1",
57-
"@capacitor/core": "^4.0.1",
56+
"@capacitor/cli": "^4.1.0",
57+
"@capacitor/core": "^4.1.0",
5858
"@ionic/cli": "^6.20.1",
59-
"@ionic/vue": "^6.2.2",
60-
"@ionic/vue-router": "^6.2.2",
59+
"@ionic/vue": "^6.2.4",
60+
"@ionic/vue-router": "^6.2.4",
6161
"@kevinmarrec/nuxt-pwa": "^0.4.2",
6262
"@nuxt/kit": "^3.0.0-rc.8",
63-
"ionicons": "^6.0.2",
64-
"pathe": "^0.3.4",
65-
"pkg-types": "^0.3.3",
63+
"ionicons": "^6.0.3",
64+
"pathe": "^0.3.5",
65+
"pkg-types": "^0.3.4",
6666
"ufo": "^0.8.5",
6767
"unimport": "^0.6.7"
6868
},
@@ -71,12 +71,12 @@
7171
"@nuxt/schema": "3.0.0-rc.8",
7272
"@nuxt/test-utils": "3.0.0-rc.8",
7373
"@nuxtjs/eslint-config-typescript": "^10.0.0",
74-
"@release-it/conventional-changelog": "^5.0.0",
75-
"@types/node": "^18.7.2",
76-
"@vitest/coverage-c8": "^0.22.0",
74+
"@release-it/conventional-changelog": "^5.1.0",
75+
"@types/node": "^18.7.14",
76+
"@vitest/coverage-c8": "^0.22.1",
7777
"c8": "^7.12.0",
7878
"conventional-changelog-conventionalcommits": "^5.0.0",
79-
"eslint": "^8.22.0",
79+
"eslint": "^8.23.0",
8080
"eslint-config-prettier": "^8.5.0",
8181
"eslint-plugin-prettier": "^4.2.1",
8282
"expect-type": "^0.13.0",
@@ -85,9 +85,9 @@
8585
"nuxt": "^3.0.0-rc.8",
8686
"pinst": "^3.0.0",
8787
"prettier": "^2.7.1",
88-
"release-it": "^15.3.0",
89-
"typescript": "^4.7.4",
90-
"vitest": "^0.22.0",
88+
"release-it": "^15.4.0",
89+
"typescript": "^4.8.2",
90+
"vitest": "^0.22.1",
9191
"vue": "^3.2.37"
9292
},
9393
"resolutions": {

playground/pages/tabs.vue

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ useHead({
2424
<ion-icon :icon="ioniconsBulbOutline" />
2525
<ion-label>Tab 3</ion-label>
2626
</ion-tab-button>
27+
28+
<ion-tab-button tab="tab4" href="/tabs/tab4">
29+
<ion-icon :icon="ioniconsAccessibilityOutline" />
30+
<ion-label>Animation examples</ion-label>
31+
</ion-tab-button>
2732
</ion-tab-bar>
2833
</ion-tabs>
2934
</ion-content>

playground/pages/tabs/tab3.vue

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
<ion-title size="large">Tab 3</ion-title>
1212
</ion-toolbar>
1313
</ion-header>
14-
1514
<ExploreContainer name="Tab 3 page" />
1615
</ion-content>
1716
</ion-page>
1817
</template>
19-

playground/pages/tabs/tab4.vue

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
<template>
2+
<ion-page>
3+
<ion-header>
4+
<ion-toolbar>
5+
<ion-title>Tab 3</ion-title>
6+
</ion-toolbar>
7+
</ion-header>
8+
<ion-content :fullscreen="true">
9+
<ion-header collapse="condense">
10+
<ion-toolbar>
11+
<ion-title size="large">Tab 3</ion-title>
12+
</ion-toolbar>
13+
</ion-header>
14+
15+
<div class="animations-grid">
16+
<section>
17+
<IonLabel color="primary">
18+
<strong>Basic animation</strong>
19+
</IonLabel>
20+
<IonAnimation
21+
ref="animation1"
22+
v-slot="{ animation }"
23+
:duration="2000"
24+
:fromTo="[
25+
{ property: 'opacity', fromValue: '1', toValue: '0.2' },
26+
{ property: 'transform', fromValue: 'translateX(0px)', toValue: 'translateX(-50px)' },
27+
]"
28+
fill="forwards"
29+
>
30+
<div class="red-square"></div>
31+
32+
<div class="buttons">
33+
<IonButton @click="animation.play()">Play</IonButton>
34+
<IonButton @click="animation.pause()">Pause</IonButton>
35+
<IonButton @click="animation.stop()">Stop</IonButton>
36+
</div>
37+
</IonAnimation>
38+
</section>
39+
40+
<section>
41+
<IonLabel color="primary">
42+
<strong>Keyframes animation</strong>
43+
</IonLabel>
44+
<IonAnimation
45+
v-slot="{ animation }"
46+
:duration="3000"
47+
:keyframes="[
48+
{ offset: 0, transform: 'scale(1) translate(0, 0)' },
49+
{ offset: 0.4, transform: 'scale(1.05) translate(15px, 15px)' },
50+
{ offset: 0.6, transform: 'scale(1.05) translate(-30px, 15px)' },
51+
{ offset: 1, transform: 'scale(1) translate(0, 0)' },
52+
]"
53+
fill="none"
54+
>
55+
<div class="blue-square"></div>
56+
57+
<div class="buttons">
58+
<IonButton @click="animation.play()">Play</IonButton>
59+
<IonButton @click="animation.pause()">Pause</IonButton>
60+
<IonButton @click="animation.stop()">Stop</IonButton>
61+
</div>
62+
</IonAnimation>
63+
</section>
64+
65+
<section>
66+
<IonLabel color="primary">
67+
<strong>Animation that repeats forever</strong>
68+
</IonLabel>
69+
<IonAnimation
70+
v-slot="{ animation }"
71+
:duration="1000"
72+
:keyframes="[
73+
{ offset: 0, transform: 'scale(1)' },
74+
{ offset: 0.75, transform: 'scale(1.05)' },
75+
{ offset: 1, transform: 'scale(1)' },
76+
]"
77+
:iterations="Infinity"
78+
>
79+
<div class="green-square"></div>
80+
81+
<div class="buttons">
82+
<IonButton @click="animation.play()">Play</IonButton>
83+
<IonButton @click="animation.pause()">Pause</IonButton>
84+
<IonButton @click="animation.stop()">Stop</IonButton>
85+
</div>
86+
</IonAnimation>
87+
</section>
88+
89+
<section>
90+
<IonLabel color="primary">
91+
<strong>Animation with style hooks</strong>
92+
</IonLabel>
93+
<IonAnimation
94+
v-slot="{ animation }"
95+
:duration="2000"
96+
:keyframes="[
97+
{ offset: 0, transform: 'scale(1)' },
98+
{ offset: 0.75, transform: 'scale(1.1)' },
99+
{ offset: 1, transform: 'scale(1)' },
100+
]"
101+
:beforeStyles="{
102+
opacity: 0.5,
103+
}"
104+
:afterClearStyles="['opacity']"
105+
:afterStyles="{
106+
transform: 'scale(0.9)',
107+
}"
108+
:beforeClearStyles="['transform']"
109+
fill="none"
110+
>
111+
<div class="red-square"></div>
112+
113+
<div class="buttons">
114+
<IonButton @click="animation.play()">Play</IonButton>
115+
<IonButton @click="animation.pause()">Pause</IonButton>
116+
<IonButton @click="animation.stop()">Stop</IonButton>
117+
</div>
118+
</IonAnimation>
119+
</section>
120+
121+
<section>
122+
<IonLabel color="primary">
123+
<strong>Animation with specific easing</strong>
124+
</IonLabel>
125+
<IonAnimation
126+
v-slot="{ animation }"
127+
:duration="2000"
128+
:keyframes="[
129+
{
130+
offset: 0,
131+
transform: 'rotate(0)',
132+
},
133+
{
134+
offset: 0.5,
135+
transform: 'rotate(60deg)',
136+
},
137+
{
138+
offset: 1,
139+
transform: 'rotate(0)',
140+
},
141+
]"
142+
easing="cubic-bezier(.7,.55,0,1.15)"
143+
>
144+
<div class="blue-square"></div>
145+
146+
<div class="buttons">
147+
<IonButton @click="animation.play()">Play</IonButton>
148+
<IonButton @click="animation.pause()">Pause</IonButton>
149+
<IonButton @click="animation.stop()">Stop</IonButton>
150+
</div>
151+
</IonAnimation>
152+
</section>
153+
154+
<section>
155+
<IonLabel color="primary">
156+
<strong>Reversed animation direction</strong>
157+
</IonLabel>
158+
<IonAnimation
159+
v-slot="{ animation }"
160+
:duration="3000"
161+
:keyframes="[
162+
{ offset: 0, transform: 'scale(1)' },
163+
{ offset: 0.3, transform: 'scale(1.2)' },
164+
{ offset: 0.6, transform: 'scale(1.05)' },
165+
{ offset: 0.9, transform: 'scale(0.8)' },
166+
{ offset: 1, transform: 'scale(1)' },
167+
]"
168+
direction="reverse"
169+
easing="ease-in"
170+
>
171+
<div class="green-square"></div>
172+
173+
<div class="buttons">
174+
<IonButton @click="animation.play()">Play</IonButton>
175+
<IonButton @click="animation.pause()">Pause</IonButton>
176+
<IonButton @click="animation.stop()">Stop</IonButton>
177+
</div>
178+
</IonAnimation>
179+
</section>
180+
</div>
181+
</ion-content>
182+
</ion-page>
183+
</template>
184+
185+
<style scoped>
186+
:root {
187+
--animation-color: purple;
188+
}
189+
190+
.animations-grid {
191+
padding: 3em;
192+
display: grid;
193+
grid-template-columns: repeat(auto-fill, minmax(460px, 1fr));
194+
grid-auto-flow: row;
195+
row-gap: 4em;
196+
align-items: center;
197+
justify-items: center;
198+
text-align: center;
199+
}
200+
201+
.animations-grid > *:nth-child(2) {
202+
margin-left: auto;
203+
margin-right: auto;
204+
}
205+
206+
.buttons {
207+
width: 100%;
208+
display: flex;
209+
justify-content: space-around;
210+
align-items: center;
211+
}
212+
213+
.red-square {
214+
width: 250px;
215+
height: 250px;
216+
background-color: red;
217+
border-radius: 1em;
218+
}
219+
220+
.blue-square {
221+
width: 250px;
222+
height: 250px;
223+
background-color: blue;
224+
border-radius: 1em;
225+
}
226+
227+
.green-square {
228+
width: 250px;
229+
height: 250px;
230+
background-color: green;
231+
border-radius: 1em;
232+
}
233+
</style>

0 commit comments

Comments
 (0)