|
|
@@ -18,13 +18,14 @@ const hasImage = computed(() => identity.value.avatarMode === 'image' && !!ident
|
|
|
const radius = computed(() => identity.value.imageCrop === 'circle' ? '50%' : identity.value.imageCrop === 'square' ? '0' : '22%')
|
|
|
const states: Record<MascotState, CursorState> = {idle:'idle',working:'working',loading:'loading',waiting:'alerting',notifying:'notifying',success:'happy',failure:'sad'}
|
|
|
const runtimeActive = computed(() => props.state !== 'idle' && !beatEnded.value)
|
|
|
+const imageActivity = computed(() => hasImage.value && runtimeActive.value && ['working', 'loading'].includes(props.state))
|
|
|
const state = computed<CursorState>(() => runtimeActive.value ? states[props.state] : oneShot.value ?? identity.value.expression)
|
|
|
const live = computed(() => visible.value && foreground.value && !reduced.value && props.animated !== false && (props.animated === true || props.size >= 56 || runtimeActive.value || !!oneShot.value || hover.value))
|
|
|
const options = computed(() => ({ state: state.value, paused: !live.value, color: identity.value.color, gaze: gaze.value,
|
|
|
- silhouette: identity.value.bodyId ? LAOA_BODIES[identity.value.bodyId] : MASCOT_SILHOUETTES[identity.value.shape === 'ellipse' ? 'oval' : identity.value.shape], fixedTime: props.fixedTime, expression: props.expressionIndex,
|
|
|
- ribbons: runtimeActive.value || state.value !== 'working' }))
|
|
|
+ silhouette: hasImage.value ? MASCOT_SILHOUETTES.circle : identity.value.bodyId ? LAOA_BODIES[identity.value.bodyId] : MASCOT_SILHOUETTES[identity.value.shape === 'ellipse' ? 'oval' : identity.value.shape], fixedTime: props.fixedTime, expression: props.expressionIndex,
|
|
|
+ effectsOnly: hasImage.value, ribbons: runtimeActive.value || state.value !== 'working' }))
|
|
|
function sync() {
|
|
|
- if (!svg.value || hasImage.value) { renderer?.dispose();renderer=undefined;return }
|
|
|
+ if (!svg.value || (hasImage.value && !imageActivity.value)) { renderer?.dispose();renderer=undefined;return }
|
|
|
if (!renderer) renderer=mountCursorAvatar(svg.value, options.value)
|
|
|
else renderer.update(options.value)
|
|
|
}
|
|
|
@@ -42,7 +43,7 @@ watch(() => [props.state, props.activityKey] as const, ([next]) => {
|
|
|
clearTimeout(beat);beatEnded.value=false;oneShot.value=undefined
|
|
|
if(props.fixedTime === undefined && ['success','failure','notifying'].includes(next)) beat=setTimeout(()=>{beatEnded.value=true},1400)
|
|
|
},{immediate:true})
|
|
|
-watch([options,svg,hasImage],sync,{flush:'post'})
|
|
|
+watch([options,svg,imageActivity],sync,{flush:'post'})
|
|
|
onMounted(()=>{
|
|
|
visibility();media=window.matchMedia?.('(prefers-reduced-motion: reduce)');motion();media?.addEventListener('change',motion)
|
|
|
document.addEventListener('visibilitychange',visibility)
|
|
|
@@ -52,11 +53,12 @@ onMounted(()=>{
|
|
|
onBeforeUnmount(()=>{renderer?.dispose();observer?.disconnect();clearTimeout(beat);media?.removeEventListener('change',motion);document.removeEventListener('visibilitychange',visibility)})
|
|
|
</script>
|
|
|
<template>
|
|
|
- <span ref="host" class="agent-avatar" :class="[`agent-avatar--${props.state}`, {'agent-avatar--image':hasImage}]" :style="{width:`${size}px`,height:`${size}px`}" role="img" :aria-label="`${name} 的头像`" @pointermove="pointer" @pointerleave="leave" :data-animated="live && !hasImage" :data-expression="state">
|
|
|
+ <span ref="host" class="agent-avatar" :class="[`agent-avatar--${props.state}`, {'agent-avatar--image':hasImage, 'agent-avatar--image-active': imageActivity && live}]" :style="{width:`${size}px`,height:`${size}px`}" role="img" :aria-label="`${name} 的头像`" @pointermove="pointer" @pointerleave="leave" :data-animated="live && (!hasImage || imageActivity)" :data-expression="state">
|
|
|
<img @error="imageFailed = true" v-if="hasImage" :src="identity.imageDataURL" alt="" :style="{borderRadius:radius}" />
|
|
|
- <svg v-else ref="svg" aria-hidden="true" />
|
|
|
+ <svg v-show="!hasImage || imageActivity" ref="svg" aria-hidden="true" />
|
|
|
</span>
|
|
|
</template>
|
|
|
<style scoped>
|
|
|
.agent-avatar{display:block;position:relative;flex:0 0 auto;overflow:visible;line-height:1;background:transparent}.agent-avatar img,.agent-avatar svg{display:block;width:100%;height:100%;object-fit:cover}
|
|
|
+.agent-avatar svg{overflow:visible;pointer-events:none}.agent-avatar--image svg{position:absolute;inset:0}.agent-avatar--image-active img{transform:scale(.72)}
|
|
|
</style>
|