Skip to content

Commit b3fd503

Browse files
authored
docs: animated logo (#2035)
* docs: wip animated logo * docs: better perf * docs: idle state * docs: idle at start * docs: ok
1 parent 18149ec commit b3fd503

File tree

5 files changed

+184
-125
lines changed

5 files changed

+184
-125
lines changed

packages/docs/.vitepress/theme/components/PiniaLogo.vue

Lines changed: 141 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
<template>
22
<svg
3-
width="408"
4-
height="520"
3+
id="pinia-logo"
54
viewBox="0 0 408 520"
65
fill="none"
76
xmlns="http://www.w3.org/2000/svg"
7+
style="z-index: 1; width: 100%"
8+
ref="svgEl"
89
>
910
<g class="leaves">
1011
<path
@@ -69,15 +70,17 @@
6970
d="M150.023 321.156C149.513 335.783 137.241 347.226 122.615 346.715C107.988 346.205 96.545 333.933 97.0557 319.307C97.5665 304.68 109.838 293.237 124.464 293.748C139.091 294.258 150.534 306.53 150.023 321.156Z"
7071
fill="white"
7172
/>
72-
<g class="eyeball">
73-
<path
74-
d="M141.046 320.343C140.719 329.726 132.847 337.067 123.463 336.739C114.08 336.411 106.739 328.539 107.067 319.156C107.395 309.773 115.267 302.432 124.65 302.76C134.033 303.087 141.374 310.959 141.046 320.343Z"
75-
fill="black"
76-
/>
77-
<path
78-
d="M125.161 316.786C125.026 320.65 121.784 323.672 117.921 323.537C114.057 323.403 111.034 320.161 111.169 316.297C111.304 312.434 114.546 309.411 118.409 309.546C122.273 309.681 125.296 312.922 125.161 316.786Z"
79-
fill="white"
80-
/>
73+
<g clip-path="url(#eye-left-mask)">
74+
<g class="eyeball">
75+
<path
76+
d="M141.046 320.343C140.719 329.726 132.847 337.067 123.463 336.739C114.08 336.411 106.739 328.539 107.067 319.156C107.395 309.773 115.267 302.432 124.65 302.76C134.033 303.087 141.374 310.959 141.046 320.343Z"
77+
fill="black"
78+
/>
79+
<path
80+
d="M125.161 316.786C125.026 320.65 121.784 323.672 117.921 323.537C114.057 323.403 111.034 320.161 111.169 316.297C111.304 312.434 114.546 309.411 118.409 309.546C122.273 309.681 125.296 312.922 125.161 316.786Z"
81+
fill="white"
82+
/>
83+
</g>
8184
</g>
8285
</template>
8386
<path
@@ -100,15 +103,17 @@
100103
d="M279.944 325.693C279.433 340.32 267.162 351.763 252.536 351.252C237.909 350.742 226.466 338.47 226.977 323.844C227.487 309.217 239.759 297.774 254.385 298.285C269.012 298.795 280.455 311.067 279.944 325.693Z"
101104
fill="white"
102105
/>
103-
<g class="eyeball">
104-
<path
105-
d="M270.967 324.879C270.64 334.263 262.767 341.604 253.384 341.276C244.001 340.948 236.66 333.076 236.988 323.693C237.316 314.31 245.188 306.969 254.571 307.297C263.954 307.624 271.295 315.496 270.967 324.879Z"
106-
fill="black"
107-
/>
108-
<path
109-
d="M255.082 321.323C254.947 325.187 251.705 328.209 247.842 328.074C243.978 327.939 240.955 324.698 241.09 320.834C241.225 316.971 244.467 313.948 248.33 314.083C252.194 314.218 255.217 317.459 255.082 321.323Z"
110-
fill="white"
111-
/>
106+
<g clip-path="url(#eye-right-mask)">
107+
<g class="eyeball">
108+
<path
109+
d="M270.967 324.879C270.64 334.263 262.767 341.604 253.384 341.276C244.001 340.948 236.66 333.076 236.988 323.693C237.316 314.31 245.188 306.969 254.571 307.297C263.954 307.624 271.295 315.496 270.967 324.879Z"
110+
fill="black"
111+
/>
112+
<path
113+
d="M255.082 321.323C254.947 325.187 251.705 328.209 247.842 328.074C243.978 327.939 240.955 324.698 241.09 320.834C241.225 316.971 244.467 313.948 248.33 314.083C252.194 314.218 255.217 317.459 255.082 321.323Z"
114+
fill="white"
115+
/>
116+
</g>
112117
</g>
113118
</template>
114119
<path
@@ -132,20 +137,20 @@
132137
d="M141.767 409.993C143.98 412.073 144.088 415.554 142.007 417.767L95.0074 467.767C92.927 469.98 89.4462 470.088 87.233 468.007C85.0197 465.927 84.9121 462.446 86.9925 460.233L133.993 410.233C136.073 408.02 139.554 407.912 141.767 409.993Z"
133138
fill="#ECB732"
134139
/>
135-
<path
136-
fill-rule="evenodd"
137-
clip-rule="evenodd"
138-
v-if="!talking || talking == 'closed'"
139-
d="M163.323 337.658C161.949 338.584 161.586 340.448 162.512 341.822C167.176 348.743 174.321 352.632 183.51 353.682C192.767 354.74 201.051 352.375 208.164 346.594C209.45 345.549 209.645 343.66 208.6 342.374C207.555 341.088 205.666 340.893 204.38 341.938C198.552 346.675 191.887 348.6 184.191 347.721C176.425 346.834 171.003 343.686 167.488 338.469C166.562 337.095 164.697 336.732 163.323 337.658Z"
140-
fill="black"
141-
/>
142-
<path
143-
v-else
144-
d="M205.5 350C205.5 355.761 203.379 359.178 200.18 361.288C196.798 363.518 191.88 364.5 186 364.5C180.12 364.5 175.202 363.518 171.82 361.288C168.621 359.178 166.5 355.761 166.5 350C166.5 347.536 168.262 344.446 171.9 341.84C175.456 339.292 180.447 337.5 186 337.5C191.553 337.5 196.544 339.292 200.1 341.84C203.738 344.446 205.5 347.536 205.5 350Z"
145-
fill="#E77777"
146-
stroke="black"
147-
stroke-width="5"
148-
/>
140+
<g class="mouth">
141+
<path
142+
class="smile"
143+
fill-rule="evenodd"
144+
clip-rule="evenodd"
145+
d="M163.323 337.658C161.949 338.584 161.586 340.448 162.512 341.822C167.176 348.743 174.321 352.632 183.51 353.682C192.767 354.74 201.051 352.375 208.164 346.594C209.45 345.549 209.645 343.66 208.6 342.374C207.555 341.088 205.666 340.893 204.38 341.938C198.552 346.675 191.887 348.6 184.191 347.721C176.425 346.834 171.003 343.686 167.488 338.469C166.562 337.095 164.697 336.732 163.323 337.658Z"
146+
fill="black"
147+
/>
148+
<path
149+
class="open"
150+
d="M213.046 343.089C213.046 356.089 199.012 367.537 186.862 367.537C174.712 367.537 164.177 356.078 164.177 343.078C164.177 335.899 175.857 332.075 188.008 332.075C200.158 332.075 213.046 335.909 213.046 343.089Z"
151+
fill="#E77777"
152+
/>
153+
</g>
149154
</g>
150155

151156
<defs>
@@ -193,32 +198,107 @@
193198
<stop stop-color="#FFE56C" />
194199
<stop offset="1" stop-color="#FFC63A" />
195200
</linearGradient>
201+
202+
<clipPath id="eye-right-mask">
203+
<circle cy="325px" cx="254" r="27" />
204+
</clipPath>
205+
<clipPath id="eye-left-mask">
206+
<circle cy="320" cx="124" r="27" />
207+
</clipPath>
196208
</defs>
197209
</svg>
198210
</template>
199211

200212
<script setup lang="ts">
201-
import { onMounted, onUnmounted, ref } from 'vue'
202-
import { useCounter } from '../stores/counter'
213+
import { computed, nextTick, onMounted, onUnmounted, reactive, ref } from 'vue'
214+
import { useSpring } from 'vue-use-spring'
215+
import {
216+
useMouse,
217+
useEventListener,
218+
useDebounceFn,
219+
useIdle,
220+
whenever,
221+
} from '@vueuse/core'
203222
204-
const blinking = ref<'open' | 'closed'>('open')
205-
const talking = ref<'open' | 'closed'>('closed')
223+
const { x: mouseX, y: mouseY } = useMouse()
224+
const mousePos = useSpring(
225+
reactive({
226+
x: mouseX,
227+
y: mouseY,
228+
}),
229+
{
230+
mass: 1,
231+
tension: 120,
232+
friction: 34,
233+
precision: 1,
234+
}
235+
)
206236
207-
const counter = useCounter()
237+
const { idle } = useIdle(3000)
238+
239+
// make it like it's looking at the user when they are idle
240+
whenever(idle, () => {
241+
const l = leftEyeCenter.value
242+
const r = rightEyeCenter.value
243+
mousePos.x = l.x + (r.x - l.x) / 2
244+
mousePos.y = r.y
245+
})
246+
247+
const svgEl = ref<SVGElement>()
248+
const leftEyeCenter = ref({ x: 0, y: 0 })
249+
const rightEyeCenter = ref({ x: 0, y: 0 })
250+
251+
function computedEyesCenter() {
252+
const svg = svgEl.value
253+
if (svg) {
254+
const leftEye = svg.querySelector<SVGElement>('.eye-left .eyeball')!
255+
const leftEyeRect = leftEye.getBoundingClientRect()
256+
leftEyeCenter.value = {
257+
x: leftEyeRect.x + leftEyeRect.width / 2,
258+
y: leftEyeRect.y + leftEyeRect.height / 2,
259+
}
260+
261+
const rightEye = svg.querySelector<SVGElement>('.eye-right .eyeball')!
262+
const rightEyeRect = rightEye.getBoundingClientRect()
263+
rightEyeCenter.value = {
264+
x: rightEyeRect.x + rightEyeRect.width / 2,
265+
y: rightEyeRect.y + rightEyeRect.height / 2,
266+
}
267+
}
268+
}
269+
270+
useEventListener('resize', useDebounceFn(computedEyesCenter, 750))
271+
272+
const leftEyeHorizontalDistance = computed(() => {
273+
return Math.min(1, Math.max(-1, (mousePos.x - leftEyeCenter.value.x) / 150))
274+
})
275+
const rightEyeHorizontalDistance = computed(() => {
276+
return Math.min(1, Math.max(-1, (mousePos.x - rightEyeCenter.value.x) / 150))
277+
})
278+
const eyeVerticalDistance = computed(() => {
279+
return Math.min(1, Math.max(-1, (mousePos.y - leftEyeCenter.value.y) / 100))
280+
})
281+
282+
const blinking = ref<'open' | 'closed'>('open')
208283
209284
const blinkTimer = 100
210-
const talkRate = 120
285+
// use a randomized blink interval for a more natural look
286+
const minBlinkInterval = 2000
287+
const maxBlinkInterval = 10000
211288
212289
onMounted(() => {
290+
nextTick(() => {
291+
computedEyesCenter()
292+
idle.value = true
293+
})
294+
213295
let timerId = setInterval(() => {
214296
let blinkState = 0
215297
function blinkHandler() {
216298
blinkState++
217299
218300
if (blinkState % 2) {
219301
blinking.value = 'closed'
220-
// counter.n++
221-
counter.$patch({ n: counter.n + 1 })
222302
setTimeout(blinkHandler, blinkTimer * 1.7)
223303
} else if (blinkState < 4) {
224304
blinking.value = 'open'
@@ -228,97 +308,36 @@ onMounted(() => {
228308
}
229309
}
230310
setTimeout(blinkHandler, 0)
231-
}, 10000)
311+
}, (maxBlinkInterval - minBlinkInterval) / 2)
232312
233313
onUnmounted(() => {
234314
clearInterval(timerId)
235315
})
236-
237-
let talkingTimer = setInterval(() => {
238-
let blinkState = 0
239-
function blinkHandler() {
240-
blinkState++
241-
242-
if (blinkState % 2) {
243-
talking.value = 'closed'
244-
setTimeout(blinkHandler, talkRate)
245-
} else if (blinkState < 10) {
246-
talking.value = 'open'
247-
setTimeout(blinkHandler, talkRate)
248-
} else {
249-
talking.value = 'closed'
250-
}
251-
}
252-
setTimeout(blinkHandler, 0)
253-
}, 5000)
254-
onUnmounted(() => {
255-
clearInterval(talkingTimer)
256-
})
257316
})
258317
</script>
259318

260-
<style>
261-
@keyframes leaves-move {
262-
40% {
263-
transform: rotate(0deg) scale(1);
264-
}
265-
45% {
266-
transform: rotate(-2deg) scale3d(0.9, 1.05, 1) translateY(6%);
267-
}
268-
47% {
269-
transform: scale3d(1.05, 0.95, 1) translateY(2%);
270-
}
271-
50% {
272-
transform: rotate(3deg) scale3d(0.9, 1.05, 1);
273-
}
274-
55% {
275-
transform: rotate(0) scale(1);
276-
}
319+
<style scoped>
320+
.eye-left .eyeball {
321+
transform: translate(
322+
calc(12px * v-bind(leftEyeHorizontalDistance)),
323+
calc(10px * v-bind(eyeVerticalDistance))
324+
);
277325
}
278326
279-
@keyframes rubberBand {
280-
29% {
281-
transform: scale3d(1, 1, 1);
282-
}
283-
284-
30% {
285-
transform: scale3d(1.1, 0.9, 1);
286-
}
287-
288-
40% {
289-
transform: scale3d(0.9, 1.1, 1);
290-
}
291-
292-
50% {
293-
transform: scale3d(1.05, 0.95, 1);
294-
}
295-
296-
65% {
297-
transform: scale3d(0.98, 1.02, 1);
298-
}
299-
300-
75% {
301-
transform: scale3d(1.02, 0.98, 1);
302-
}
303-
304-
76% {
305-
transform: scale3d(1, 1, 1);
306-
}
327+
.eye-right .eyeball {
328+
transform: translate(
329+
calc(12px * v-bind(rightEyeHorizontalDistance)),
330+
calc(10px * v-bind(eyeVerticalDistance))
331+
);
307332
}
308-
</style>
309333
310-
<style scoped>
311-
.leaves {
312-
animation: 500ms ease-in-out infinite rubberBand;
313-
animation: 3s ease-in-out 0s infinite normal leaves-move;
314-
transform-origin: bottom;
334+
#pinia-logo .mouth .open {
335+
visibility: hidden;
315336
}
316-
317-
.body {
318-
animation-duration: 500ms;
319-
animation-delay: 0s;
320-
animation-iteration-count: 0;
321-
animation-name: rubberBand;
322-
transform-origin: center;
337+
#pinia-logo:hover .mouth .open {
338+
visibility: unset;
339+
}
340+
#pinia-logo:hover .mouth .smile {
341+
visibility: hidden;
323342
}
324343
</style>

packages/docs/.vitepress/theme/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import DefaultTheme from 'vitepress/theme'
44
import AsideSponsors from './components/AsideSponsors.vue'
55
// import AsideSponsors from './components/AsideSponsors.vue'
66
import TranslationStatus from 'vitepress-translation-helper/ui/TranslationStatus.vue'
7+
// import HomeSponsors from './components/HomeSponsors.vue'
8+
import PiniaLogo from './components/PiniaLogo.vue'
79
import './styles/vars.css'
810
import './styles/playground-links.css'
911
import VueSchoolLink from './components/VueSchoolLink.vue'
@@ -20,6 +22,7 @@ const theme: Theme = {
2022
...DefaultTheme,
2123
Layout() {
2224
return h(DefaultTheme.Layout, null, {
25+
'home-hero-image': () => h('div', { class: 'image-src' }, h(PiniaLogo)),
2326
// 'home-features-after': () => h(HomeSponsors),
2427
'aside-ads-before': () => h(AsideSponsors),
2528
// 'layout-top': () => h(VuejsdeConfBanner),

packages/docs/api/pinia/functions/mapActions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,11 +64,11 @@ export default {
6464
methods: {
6565
// other methods properties
6666
// useCounterStore has two actions named `increment` and `setCount`
67-
...mapActions(useCounterStore, { moar: 'increment', setIt: 'setCount' })
67+
...mapActions(useCounterStore, { more: 'increment', setIt: 'setCount' })
6868
},
6969

7070
created() {
71-
this.moar()
71+
this.more()
7272
this.setIt(2)
7373
}
7474
}

packages/docs/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@vueuse/core": "^10.11.0",
1818
"pinia": "workspace:*",
1919
"vitepress": "1.2.3",
20-
"vitepress-translation-helper": "^0.2.1"
20+
"vitepress-translation-helper": "^0.2.1",
21+
"vue-use-spring": "^0.1.1"
2122
}
2223
}

0 commit comments

Comments
 (0)