fix(animations): animations played before necessary / on page load, files location;

Fixed animation being played right after pages load, created `.preventAnimLoad` (used on a parent element that's being loaded), and `.preventAnim` classes (used on the element with the animation to be prevented) to this;

Moved `TaskList.vue` and `TaskComp.vue` to Task (`components/Task`) for better organization.
This commit is contained in:
Guz
2022-01-30 17:50:27 -03:00
parent 6a5cf5edda
commit 51c4618d9c
6 changed files with 368 additions and 301 deletions

View File

@@ -11,3 +11,6 @@ $dark-secondary: $eerie-black3;
$main-font: 'Red Display', arial, helvetica, sans-serif;
$secondary-font: 'Fira Code', monospace, sans-serif;
$checked-color: $green0;
$unchecked-color: $begonia-red0;

View File

@@ -34,6 +34,10 @@ body {
}
}
.preventAnimLoad * .preventAnim {
animation-duration: 0s !important;
}
/*
? Workaround with changing elements depending on the page's theme,
? probably there is a better way, using `v-if` and `v-else`, but apparently,

View File

@@ -0,0 +1,346 @@
<template>
<li
:id="`task${id}`"
:class="`${preventAnim ? 'preventAnimLoad ' : ''}${
newTask ? 'newTask ' : ''
}taskContainer`"
>
<div class="taskControl">
<div :class="`task`">
<div class="taskInfo">
<label
class="checkbox"
:for="`checkboxInput${id}`"
@click="changeState()"
>
<input
:id="`checkboxInput${id}`"
type="checkbox"
:checked="checked"
/>
<div class="icons animate preventAnim">
<IconChecked class="checkedIcon" />
<IconUnchecked class="uncheckedIcon" />
</div>
</label>
<div class="info">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
</div>
</div>
<nav class="controls">
<button class="deleteButton" @click="deleteTask()">
<IconTrash class="trashIcon" />
</button>
</nav>
</div>
</li>
</template>
<script lang="ts">
import Vue from 'vue';
import sm from '~/libs/storageManagement';
export default Vue.extend({
name: 'TaskComp',
props: {
id: {
type: Number,
required: true,
},
title: {
type: String,
default: 'Task Title',
},
description: {
type: String,
default: 'Task short description',
},
checked: {
type: Boolean,
default: false,
},
newTask: {
type: Boolean,
default: false,
},
},
data() {
return {
openSubTasks: false,
preventAnim: true,
};
},
mounted() {
setTimeout(() => {
this.preventAnim = false;
}, 1000);
},
methods: {
changeState() {
const element = document.querySelector(
`input#checkboxInput${this.id}`
) as HTMLInputElement;
const tasks = sm.get('tasks');
tasks[this.id].checked = element?.checked;
this.openSubTasks = false;
sm.set('tasks', tasks);
},
deleteTask() {
const localTasks = sm.get('tasks');
localTasks.splice(this.id, 1);
sm.set('tasks', localTasks);
},
},
});
</script>
<style lang="scss">
@import '~assets/styles/_variables.scss';
@import '~assets/styles/_mixins.scss';
.newTask {
animation-delay: 0s !important;
}
.taskContainer {
@for $i from 1 through 100 {
&#task#{$i} {
animation-delay: #{$i * 0.2}s;
}
}
opacity: 0;
transform: translateY(50%);
animation: enter 0.5s ease-out;
animation-fill-mode: forwards;
@keyframes enter {
from {
opacity: 0;
transform: translateY(50%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.taskControl {
display: flex;
margin-top: 5%;
display: flex;
align-items: center;
justify-content: space-between;
.controls {
position: absolute;
right: -6%;
opacity: 0;
@media (pointer: none), (pointer: coarse) {
opacity: 0.5;
}
transition: opacity 0.2s;
transition: right 0.3s;
button {
background-color: transparent;
padding: 1px;
border: 0;
cursor: pointer;
}
.deleteButton {
.trashIcon * {
transition: stroke 0.2s;
}
&:hover .trashIcon * {
stroke: $begonia-red0;
}
}
}
&:hover .controls {
transition: 0.2s 0.5s;
opacity: 1;
right: -7%;
}
.task {
.light-mode & {
background-color: $blue0;
color: $independence-gray0;
}
.dark-mode & {
background-color: $eerie-black0;
color: $dark-primary;
}
position: relative;
width: 100%;
z-index: 4;
border-radius: 20px;
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 5%;
.taskInfo {
display: flex;
text-align: left;
align-items: center;
width: 100%;
.checkbox {
padding: 1%;
padding-right: 2%;
input {
display: none;
}
input[type='checkbox']:checked + .icons {
animation: iconAnim 1s;
.checkedIcon {
display: inline-block;
}
.uncheckedIcon {
display: none;
}
.checkedIcon *,
.uncheckedIcon * {
.light-mode & {
@include colorFadeAnim(
$unchecked-color,
$checked-color,
1s,
'checkLight'
);
stroke: $checked-color;
}
.dark-mode & {
@include colorFadeAnim(
$unchecked-color,
$checked-color,
1s,
'checkDark'
);
stroke: $checked-color;
}
}
}
input[type='checkbox']:not(:checked) + .icons {
animation: iconAnimReverse 1s;
.checkedIcon {
display: none;
}
.uncheckedIcon {
display: inline-block;
}
.checkedIcon *,
.uncheckedIcon * {
.light-mode & {
@include colorFadeAnim(
$checked-color,
$unchecked-color,
1s,
'uncheckLight'
);
stroke: $unchecked-color;
}
.dark-mode & {
@include colorFadeAnim(
$checked-color,
$unchecked-color,
1s,
'uncheckDark'
);
stroke: $unchecked-color;
}
}
}
.icons {
@include center;
$bg-color: #00000080;
background-color: $bg-color;
margin: 2px;
padding: 2px;
border-radius: 50%;
.checkedIcon *,
.uncheckedIcon * {
.light-mode & {
stroke: $unchecked-color;
}
.dark-mode & {
stroke: $unchecked-color;
}
}
}
@keyframes iconAnim {
from {
transform: rotate(0) scale(1);
}
5% {
transform: rotate(0) scale(0.7);
}
50% {
transform: rotate(360deg) scale(1.2);
}
90% {
transform: scale(1.2);
}
to {
transform: scale(1);
}
}
@keyframes iconAnimReverse {
from {
transform: rotate(0) scale(1);
}
5% {
transform: rotate(0) scale(0.7);
}
50% {
transform: rotate(-360deg) scale(1);
}
to {
transform: rotate(-360deg) scale(1);
}
}
}
.info {
display: inline-block;
h1 {
font-size: 1.2em;
margin: 0;
}
p {
font-family: $secondary-font;
font-size: 0.8em;
margin: 0;
}
}
}
}
}
}
</style>

View File

@@ -24,6 +24,8 @@ const taskItem = {
description: 'Task',
checked: false,
newTask: true,
autoCheck: true,
subTasks: [],
};
export default Vue.extend({
@@ -37,7 +39,7 @@ export default Vue.extend({
const localTasks = sm.get('tasks');
for(const task of localTasks) task.newTask = undefined;
for (const task of localTasks) task.newTask = undefined;
sm.set('tasks', localTasks);
},

View File

@@ -1,298 +0,0 @@
<template>
<li :id="`task${id}`" :class="`${newTask ? 'newTask ' : ''}task`">
<div class="taskInfo">
<label
class="checkbox"
:for="`checkboxInput${id}`"
@click="changeState()"
>
<input :id="`checkboxInput${id}`" type="checkbox" :checked="checked" />
<div class="icons animate">
<IconChecked class="checkedIcon" />
<IconUnchecked class="uncheckedIcon" />
</div>
</label>
<div class="info">
<h1>{{ title }}</h1>
<p>{{ description }}</p>
</div>
</div>
<nav class="controls">
<button class="deleteButton" @click="deleteTask()">
<IconTrash class="trashIcon" />
</button>
</nav>
</li>
</template>
<script lang="js">
import Vue from 'vue';
import sm from '~/libs/storageManagement';
export default Vue.extend({
name: 'TaskComp',
props: {
id: {
type: Number,
required: true,
},
title: {
type: String,
default: 'Task Title',
},
description: {
type: String,
default: 'Task short description',
},
checked: {
type: Boolean,
default: false,
},
newTask: {
type: Boolean,
default: false,
}
},
methods: {
changeState() {
const state = document.querySelector(`input#checkboxInput${this.id}`)?.checked;
const tasks = sm.get('tasks');
tasks[this.id].checked = state;
sm.set('tasks', tasks);
},
deleteTask() {
const localTasks = sm.get('tasks');
localTasks.splice(this.id, 1);
sm.set('tasks', localTasks);
}
}
});
</script>
<style lang="scss">
@import '~assets/styles/_variables.scss';
@import '~assets/styles/_mixins.scss';
$checked-color: $green0;
$unchecked-color: $begonia-red0;
.newTask {
animation-delay: 0s !important;
}
@for $i from 1 through 100 {
.task#task#{$i} {
animation-delay: #{$i * 0.2}s;
}
}
.task {
.light-mode & {
background-color: $blue0;
color: $independence-gray0;
}
.dark-mode & {
background-color: $eerie-black0;
color: $dark-primary;
}
border-radius: 20px;
margin-top: 5%;
padding: 10px;
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 5%;
opacity: 0;
transform: translateY(50%);
animation: enter 0.5s ease-out;
animation-fill-mode: forwards;
@keyframes enter {
from {
opacity: 0;
transform: translateY(50%);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.taskInfo {
display: flex;
text-align: left;
align-items: center;
width: 100%;
.checkbox {
padding: 1%;
padding-right: 2%;
input {
display: none;
}
input[type='checkbox']:checked + .icons {
animation: iconAnim 1s;
.checkedIcon {
display: inline-block;
}
.uncheckedIcon {
display: none;
}
.checkedIcon *,
.uncheckedIcon * {
.light-mode & {
@include colorFadeAnim(
$unchecked-color,
$checked-color,
1s,
'checkLight'
);
stroke: $checked-color;
}
.dark-mode & {
@include colorFadeAnim(
$unchecked-color,
$checked-color,
1s,
'checkDark'
);
stroke: $checked-color;
}
}
}
input[type='checkbox']:not(:checked) + .icons {
animation: iconAnimReverse 1s;
.checkedIcon {
display: none;
}
.uncheckedIcon {
display: inline-block;
}
.checkedIcon *,
.uncheckedIcon * {
.light-mode & {
@include colorFadeAnim(
$checked-color,
$unchecked-color,
1s,
'uncheckLight'
);
stroke: $unchecked-color;
}
.dark-mode & {
@include colorFadeAnim(
$checked-color,
$unchecked-color,
1s,
'uncheckDark'
);
stroke: $unchecked-color;
}
}
}
.icons {
@include center;
$bg-color: #00000080;
background-color: $bg-color;
margin: 2px;
padding: 2px;
border-radius: 50%;
.checkedIcon *,
.uncheckedIcon * {
.light-mode & {
stroke: $unchecked-color;
}
.dark-mode & {
stroke: $unchecked-color;
}
}
}
@keyframes iconAnim {
from {
transform: rotate(0) scale(1);
}
5% {
transform: rotate(0) scale(0.7);
}
50% {
transform: rotate(360deg) scale(1.2);
}
90% {
transform: scale(1.2);
}
to {
transform: scale(1);
}
}
@keyframes iconAnimReverse {
from {
transform: rotate(0) scale(1);
}
5% {
transform: rotate(0) scale(0.7);
}
50% {
transform: rotate(-360deg) scale(1);
}
to {
transform: rotate(-360deg) scale(1);
}
}
}
.info {
display: inline-block;
h1 {
font-size: 1.2em;
margin: 0;
}
p {
font-family: $secondary-font;
font-size: 0.8em;
margin: 0;
}
}
}
.controls {
opacity: 0;
@media (pointer: none), (pointer: coarse) {
opacity: 0.5;
}
transition: opacity 0.2s;
button {
background-color: transparent;
padding: 1px;
border: 0;
cursor: pointer;
}
.trashIcon {
* {
transition: stroke 0.2s;
}
&:hover * {
stroke: $unchecked-color;
}
}
}
&:hover .controls {
opacity: 1;
}
}
</style>

View File

@@ -1,12 +1,12 @@
<template>
<main class="container">
<main :class="`${preventAnim ? 'preventAnimLoad ' : ''}container`">
<div>
<ToTodayLogo />
<p>A single page web app to help yours day-to-day tasks</p>
<ThemePicker />
</div>
<div>
<TaskList/>
<TaskList />
</div>
<PageFooter />
</main>
@@ -16,5 +16,15 @@
import Vue from 'vue';
export default Vue.extend({
name: 'IndexPage',
data() {
return {
preventAnim: true,
};
},
mounted() {
setTimeout(() => {
this.preventAnim = false;
}, 1000);
},
});
</script>