#app
.main-desc
| see more details in
a(href="https://vuejs.org/guide/built-ins/transition.html", target="_blank") Vue transition
transition(name="fade", @enter="onWrapperEnter")
.modals-wrapper(v-if="initWrapper")
transition-group(name="modals", @after-leave="onModalLeave")
template(v-for="(data, key) in modalList", :key="key")
.modal-mask(v-if="initModal && data")
.modal-wrapper
.title(:style="{color: data.color}") {{ data.name }}
.close-btn(@click="removeModal") close
.open-btn(@click="addModal") open
View Compiled
@import url('https://fonts.googleapis.com/css2?family=Rubik+Puddles&display=swap&text=abcdefghijklmnopqrstuvwxyz0123456789');
@import url('https://fonts.googleapis.com/css2?family=Concert+One&display=swap');
* {
box-sizing: border-box;
}
html {
font-size: 100vmax / 1600 * 100;
@media (max-width: 992px) {
font-size: 60px;
}
}
@mixin flexCenter {
display: flex;
justify-content: center;
align-items: center;
}
@mixin fullCover($pos: absolute) {
position: $pos;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
$default-font: 'Concert One', cursive;
body {
position: relative;
min-height: 100vh;
@include flexCenter;
font-family: $default-font;
font-size: 0.24rem;
text-align: center;
background: url(https://images.unsplash.com/photo-1539096522021-2de4703b0271?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTY0OTIyNDQ4Nw&ixlib=rb-1.2.1&q=85) no-repeat center 30% / cover;
}
.main-desc {
border-radius: 0.2rem;
background-color: #fff;
padding: 0.1rem 0.3rem;
}
.open-btn {
position: absolute;
bottom: 0.2rem;
right: 0.2rem;
padding: 0.1rem 0.3rem;
color: #ffcc33;
text-transform: uppercase;
border-radius: 0.2rem;
background-color: #333;
box-shadow: 3px 5px 0 #ccc;
cursor: pointer;
z-index: 100;
}
.modals-wrapper {
@include fullCover(fixed);
background-color: rgba(#000, 0.5);
}
.modal-mask {
@include fullCover;
@include flexCenter;
.modal-wrapper {
position: relative;
width: 5rem;
height: 3rem;
@include flexCenter;
background-color: #333;
border-radius: 0.2rem;
filter: drop-shadow(0 0 10px rgba(#000, 0.5));
&:after {
content: '';
@include fullCover;
backdrop-filter: brightness(0.3);
z-index: 1;
}
}
&:last-of-type .modal-wrapper {
&:after {
display: none;
}
}
.title {
font-size: 0.6rem;
font-family: 'Rubik Puddles', $default-font;
}
.close-btn {
position: absolute;
top: 0.1rem;
right: 0.2rem;
color: #ffcc33;
font-size: 0.2rem;
text-transform: uppercase;
cursor: pointer;
}
}
// animation
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.modals-enter-active,
.modals-leave-active {
transition: opacity 0.3s;
.modal-wrapper,
.rule-modal-wrapper {
transition: transform 0.3s;
}
}
.modals-enter-from,
.modals-leave-to {
opacity: 0;
.modal-wrapper,
.rule-modal-wrapper {
transform: translateY(20%);
}
}
View Compiled
// utils
const randomHash = () => Math.random().toString(36).substring(2);
const randomRange = (min, max) => Math.floor(Math.random() * (max - min + 1) ) + min;
const getRandomColor = () => {
const colorDeg = randomRange(0, 360);
const contrast = randomRange(30, 50);
const lightness = randomRange(60, 80);
return `hsl(${colorDeg}deg, ${contrast}%, ${lightness}%)`;
}
const { ref, watch, onMounted } = Vue;
const App = {
setup() {
const initWrapper = ref(false);
const initModal = ref(false);
const modalList = ref([]);
watch(() => modalList.value.length, (val) => {
if (val && !initWrapper.value) initWrapper.value = true;
})
// transition
const onWrapperEnter = () => initModal.value = true;
const onModalLeave = () => {
if (modalList.value.length) return;
initWrapper.value = false;
initModal.value = false;
}
// modal
const addModal = () => {
modalList.value.push({
name: randomHash(),
color: getRandomColor(),
});
}
const removeModal = () => {
modalList.value.pop();
}
onMounted(() => addModal());
return {
initWrapper,
initModal,
modalList,
onWrapperEnter,
onModalLeave,
addModal,
removeModal,
}
},
}
Vue.createApp(App).mount('#app');
This Pen doesn't use any external CSS resources.