vue-color-avatar/src/App.vue

301 lines
6.7 KiB
Vue
Raw Normal View History

2021-10-16 10:29:32 +00:00
<template>
<main class="main">
<Container>
<div class="content-warpper">
<div class="content-view">
<Header />
<div class="playground">
<div class="avatar-wrapper">
<VueColorAvatar
ref="colorAvatarRef"
:option="avatarOption"
:size="280"
:style="{
transform: `rotateY(${flipped ? -180 : 0}deg)`,
}"
/>
</div>
<ActionBar @actionHandler="handleAction" />
<div class="action-group">
<button class="action-randomize" @click="handleGenerate">
{{ t('action.randomize') }}
</button>
<button
class="action-download"
:disabled="downloading"
@click="handleDownload"
>
{{
downloading
? `${t('action.downloading')}...`
: t('action.download')
}}
</button>
</div>
</div>
<Footer />
<CodeModal :visible="codeVisible" @close="codeVisible = false" />
<DownloadModal
:visible="downloadModalVisible"
:image-url="imageDataURL"
@close=";(downloadModalVisible = false), (imageDataURL = '')"
/>
</div>
<Confetti />
<div class="gradient-bg">
<div class="gradient-top"></div>
<div class="gradient-bottom"></div>
</div>
</div>
</Container>
<Sider>
<Configurator />
</Sider>
</main>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { useI18n } from 'vue-i18n'
import ActionBar from '@/components/ActionBar.vue'
import CodeModal from '@/components/CodeModal.vue'
import Configurator from '@/components/Configurator.vue'
import DownloadModal from '@/components/DownloadModal.vue'
import type { VueColorAvatarRef } from '@/components/VueColorAvatar.vue'
import VueColorAvatar from '@/components/VueColorAvatar.vue'
import { ActionType } from '@/enums'
import { useAvatarOption } from '@/hooks'
import Container from '@/layouts/Container.vue'
import Footer from '@/layouts/Footer.vue'
import Header from '@/layouts/Header.vue'
import Sider from '@/layouts/Sider.vue'
import { useStore } from '@/store'
import { REDO, UNDO } from '@/store/mutation-type'
import {
getRandomAvatarOption,
getSpecialAvatarOption,
showConfetti,
} from '@/utils'
import {
DOWNLOAD_DELAY,
NOT_COMPATIBLE_AGENTS,
TRIGGER_PROBABILITY,
} from '@/utils/constant'
2021-10-17 09:32:01 +00:00
import { recordClick } from '@/utils/ga'
2021-10-16 10:29:32 +00:00
import Confetti from './components/Confetti.vue'
const store = useStore()
const [avatarOption, setAvatarOption] = useAvatarOption()
const { t } = useI18n()
const colorAvatarRef = ref<VueColorAvatarRef>()
function handleGenerate() {
if (Math.random() <= TRIGGER_PROBABILITY) {
let colorfulOption = getSpecialAvatarOption()
while (
JSON.stringify(colorfulOption) === JSON.stringify(avatarOption.value)
) {
colorfulOption = getSpecialAvatarOption()
}
setAvatarOption(colorfulOption)
showConfetti()
} else {
const randomOption = getRandomAvatarOption(avatarOption.value)
setAvatarOption(randomOption)
}
}
const downloadModalVisible = ref(false)
const downloading = ref(false)
const imageDataURL = ref('')
async function handleDownload() {
try {
downloading.value = true
const avatarEle = colorAvatarRef.value?.avatarRef
const userAgent = window.navigator.userAgent.toLowerCase()
const notCompatible = NOT_COMPATIBLE_AGENTS.some(
(agent) => userAgent.indexOf(agent) !== -1
)
if (avatarEle) {
const html2canvas = (await import('html2canvas')).default
const canvas = await html2canvas(avatarEle, {
backgroundColor: null,
})
const dataURL = canvas.toDataURL()
if (notCompatible) {
imageDataURL.value = dataURL
downloadModalVisible.value = true
} else {
const trigger = document.createElement('a')
trigger.href = dataURL
trigger.download = 'vue-color-avatar.png'
trigger.click()
}
}
} finally {
setTimeout(() => {
downloading.value = false
}, DOWNLOAD_DELAY)
}
}
const flipped = ref(false)
const codeVisible = ref(false)
function handleAction(actionType: ActionType) {
switch (actionType) {
case ActionType.Undo:
store.commit(UNDO)
break
case ActionType.Redo:
store.commit(REDO)
break
case ActionType.Flip:
flipped.value = !flipped.value
break
case ActionType.Code:
codeVisible.value = !codeVisible.value
2021-10-17 09:32:01 +00:00
recordClick({
event_category: 'code',
event_label: 'view code',
})
2021-10-16 10:29:32 +00:00
break
}
}
</script>
<style lang="scss" scoped>
.main {
width: 100%;
height: 100%;
overflow: hidden;
color: $color-text;
background-color: $color-page-bg;
.content-warpper {
height: 100%;
transform: scale(1);
.content-view {
position: relative;
z-index: 110;
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto;
}
}
}
.playground {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 2rem 0;
.avatar-wrapper {
display: flex;
align-items: center;
justify-content: center;
@media screen and (max-width: $screen-sm) {
transform: scale(0.85);
}
}
.action-group {
display: flex;
align-items: center;
justify-content: center;
margin-top: 4rem;
.action-randomize,
.action-download {
min-width: 6rem;
height: 2.5rem;
margin: 0 1rem;
padding: 0 1rem;
color: $color-text;
font-weight: bold;
background: $color-gray;
border-radius: 0.6rem;
cursor: pointer;
transition: color 0.2s;
user-select: none;
&:hover {
color: lighten($color-text, 10);
}
2021-10-17 03:13:46 +00:00
&:disabled,
&[disabled] {
2021-10-16 10:29:32 +00:00
color: rgba($color-text, 0.5);
cursor: default;
}
}
}
}
.gradient-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
@mixin gradient-block($color) {
position: absolute;
width: 100vh;
height: 100vh;
background-image: radial-gradient(
rgba($color, 0.8) 20%,
rgba($color, 0.6) 40%,
rgba($color, 0.4) 60%,
rgba($color, 0.2) 80%,
transparent 100%
);
border-radius: 50%;
opacity: 0.2;
filter: blur(4rem);
}
.gradient-top {
@include gradient-block($color-secondary);
top: -50%;
right: -20%;
}
.gradient-bottom {
@include gradient-block($color-accent);
bottom: -50%;
left: -20%;
}
}
</style>