Added dialog animations, still working on box shadows

This commit is contained in:
Stefan Zermatten
2019-01-23 16:49:58 +02:00
parent e8a0e86548
commit 00e8cbc1c8
9 changed files with 174 additions and 20 deletions

View File

@@ -41,6 +41,7 @@
import AbilityListTile from '/imports/ui/components/AbilityListTile.Story.vue';
import AttributeCard from '/imports/ui/components/AttributeCard.Story.vue';
import ColumnLayout from "/imports/ui/components/ColumnLayout.Story.vue";
import DialogStack from '/imports/ui/dialogStack/DialogStack.Story.vue';
import HealthBar from '/imports/ui/components/HealthBar.Story.vue';
import HitDiceListTile from '/imports/ui/components/HitDiceListTile.Story.vue';
import SkillListTile from '/imports/ui/components/SkillListTile.Story.vue';
@@ -50,6 +51,7 @@
AbilityListTile,
AttributeCard,
ColumnLayout,
DialogStack,
HealthBar,
HitDiceListTile,
SkillListTile,

View File

@@ -4,7 +4,7 @@
Test Dialog
</div>
<div>
<v-btn @click="openDialog">Open Dialog</v-btn>
<v-btn @click="openDialog(_uid + 'btn')" :id="_uid + 'btn'">Open Dialog</v-btn>
</div>
</dialog-base>
</template>
@@ -14,9 +14,10 @@
import DialogBase from "/imports/ui/dialogStack/DialogBase.vue";
const component = {
methods: {
openDialog(event){
openDialog(elementId){
store.commit("pushDialogStack", {
component,
elementId,
});
}
},

View File

@@ -0,0 +1,22 @@
<template lang="html">
<v-card-text>
<v-layout align-center justify-center>
<v-btn @click="openDialog(_uid + 'btn')" :id="_uid + 'btn'"/>
</v-layout>
</v-card-text>
</template>
<script>
import DialogBaseStory from '/imports/ui/dialogStack/DialogBase.Story.vue';
import store from "/imports/ui/vuexStore.js";
export default {
methods: {
openDialog(elementId){
store.commit("pushDialogStack", {
component: DialogBaseStory,
elementId,
});
}
},
}
</script>

View File

@@ -5,14 +5,23 @@
@click="backdropClicked"
:class="dialogs.length ? '' : 'hidden' "
></div>
<transition-group name="dialog-list" class="dialog-sizer" tag="div">
<transition-group
name="dialog-list"
class="dialog-sizer"
tag="div"
@enter="enter"
@leave="leave"
>
<div class="sibling" key="sibling"/>
<div
v-for="(dialog, index) in dialogs"
:key="dialog._id"
class="dialog"
:style="getDialogStyle(index)"
:data-element-id="dialog.elementId"
:data-index="index"
:style="getDialogStyle(index)"
>
<component :is="dialog.component" :data="dialog.data" @pop="popDialogStack($event)"></component>
<component :is="dialog.component" :data="dialog.data" @pop="popDialogStack($event)" class="dialog-component"></component>
</div>
</transition-group>
</v-layout>
@@ -22,8 +31,11 @@
import "/imports/ui/dialogStack/dialogStackWindowEvents.js";
import store from "/imports/ui/vuexStore.js";
import anime from "animejs";
import mockElement from '/imports/ui/dialogStack/mockElement.js';
import Vue from "vue";
const offset = 16;
const OFFSET = 16;
const MOCK_DURATION = 8000; // Keep in sync with css transition of .dialog
export default {
computed: {
@@ -42,10 +54,57 @@
const length = store.state.dialogStack.dialogs.length;
if (index >= length) return;
const num = length - 1;
const left = (num - index) * -offset;
const top = (num - index) * -offset;
const left = (num - index) * -OFFSET;
const top = (num - index) * -OFFSET;
return `left:${left}px; top:${top}px;`;
},
enter(target, done){
let elementId = target.attributes['data-element-id'].value;
let source = document.getElementById(elementId);
// Get the original styles so we can repair them later
let originalStyle = {
transform: target.style.transform,
background: target.style.background,
borderRadius: target.style.borderRadius,
transition: target.style.transition,
boxShadow: target.style.boxShadow,
sourceTransition: source.style.transition,
}
// hide the source
source.style.transition = "none";
source.style.visibility = "hidden";
// Instantly mock the source
target.style.transition = 'none';
mockElement({source, target});
// After a full tick, repair the original styles
Vue.nextTick(() => {
target.style.transform = originalStyle.transform;
target.style.background = originalStyle.background;
target.style.borderRadius = originalStyle.borderRadius;
target.style.transition = originalStyle.transition;
target.style.boxShadow = originalStyle.boxShadow;
source.style.transition = originalStyle.sourceTransition;
setTimeout(done, MOCK_DURATION);
});
},
leave(target, done){
let elementId = target.attributes['data-element-id'].value;
let source = document.getElementById(elementId);
let index = target.attributes['data-index'].value;
if (index != 0){
// If we aren't the only dialog, we'll need compensate for offset
mockElement({source, target, offset: {x: OFFSET, y: OFFSET}})
} else {
mockElement({source, target});
}
setTimeout(() => {
source.style.visibility = null;
done();
}, MOCK_DURATION);
}
},
};
</script>
@@ -58,6 +117,7 @@
right: 0;
bottom: 0;
pointer-events: none;
z-index: 3;
}
.dialog-sizer {
position: relative;
@@ -79,17 +139,25 @@
.backdrop.hidden {
display: none
}
.dialog-list-move {
transition: transform 400ms;
}
.dialog-list-leave-active {
}
.dialog-list-enter .dialog-component, .dialog-list-leave-to .dialog-component {
opacity: 0;
}
.dialog-list-enter-active .dialog-component {
transition: opacity 4s;
}
.dialog-list-leave-active .dialog-component {
transition: opacity 4s 4s;
}
.dialog {
transition: all 8s;
transform-origin: top left;
position: absolute;
height: 100%;
width: 100%;
pointer-events: initial;
z-index: 1;
overflow: hidden;
background: white;
}
.dialog > * {
height: 100%;

View File

@@ -7,14 +7,15 @@ const dialogStackStore = {
currentResult: null,
},
mutations: {
pushDialogStack(state, {component, data, element, returnElement, callback}){
pushDialogStack(state, {component, data, elementId, returnElement, callback}){
// Generate a new _id so that Vue knows how to shuffle the array
console.log({elementId});
const _id = Random.id();
state.dialogs.push({
_id,
component,
data,
element,
elementId,
returnElement,
callback,
});

View File

@@ -0,0 +1,58 @@
import { parse, stringify } from 'css-box-shadow';
// Only supports border radius defined like "20px" or "100%"
const transformedRadius = (radiusString, deltaWidth, deltaHeight) => {
if (/^\d+\.?\d*px$/.test(radiusString)){
//The radius is defined in pixel units, so get the radius as a number
const rad = +radiusString.match(/\d+\.?\d*/)[0];
// Set the x and y radius of the "to" element, compensating for scale
return `${rad / deltaWidth}px / ${rad / deltaHeight}px`;
} else if (/^\d+\.?\d*%$/.test(radiusString)) {
//The radius is defined as a percentage, so just use it as is
return radiusString;
}
};
const transformedBoxShadow = (shadowString, deltaWidth, deltaHeight) => {
if (shadowString[0] === 'r'){
let strings = shadowString.match(/rgba\([^)]+\)[^,]+/g);
strings = strings.map(string => {
// TODO move color to end
strings.match(/(rgba\([^)]+\))([^,]+)/)
});
}
let scaleAverage = (deltaWidth + deltaHeight) / 2;
let shadows = parse(shadowString);
console.log({shadowString, shadows});
shadows.forEach(shadow => {
shadow.offsetX /= deltaWidth;
shadow.offsetY /= deltaHeight;
shadow.blurRadius /= scaleAverage;
shadow.spreadRadius /= scaleAverage;
})
console.log({newShadows: shadows});
return stringify(shadows);
}
export default function mockElement({source, target, offset = {x: 0, y: 0}}){
if (!source || !target) throw `Can't mock without ${source ? 'target' : 'source'}` ;
sourceRect = source.getBoundingClientRect();
targetRect = target.getBoundingClientRect();
// Get how must the target change to become the source
const deltaWidth = sourceRect.width / targetRect.width;
const deltaHeight = sourceRect.height / targetRect.height;
const deltaLeft = sourceRect.left - targetRect.left + offset.x;
const deltaTop = sourceRect.top - targetRect.top + offset.y;
// Mock the source
target.style.transform = `translate(${deltaLeft}px, ${deltaTop}px) ` +
`scale(${deltaWidth}, ${deltaHeight})`;
target.style.background = getComputedStyle(source).background;
target.style.borderRadius = transformedRadius(
getComputedStyle(source).borderRadius, deltaWidth, deltaHeight
);
//target.style.boxShadow = transformedBoxShadow(
// getComputedStyle(source).boxShadow, deltaWidth, deltaHeight
//);
};

View File

@@ -8,7 +8,6 @@ import CharacterSheetPage from "/imports/ui/pages/CharacterSheetPage.vue";
import SignIn from "/imports/ui/pages/SignIn.vue" ;
import Register from "/imports/ui/pages/Register.vue" ;
import Account from "/imports/ui/pages/Account.vue" ;
import TestDialog from "/imports/ui/dialogStack/TestDialog.vue";
// Not found
import NotFound from '/imports/ui/pages/NotFound.vue';
@@ -45,9 +44,6 @@ RouterFactory.configure(factory => {
},{
path: "/account",
component: Account,
},{
path: "/test-dialog",
component: TestDialog,
},
]);
//Development routes

5
app/package-lock.json generated
View File

@@ -243,6 +243,11 @@
}
}
},
"css-box-shadow": {
"version": "1.0.0-3",
"resolved": "https://registry.npmjs.org/css-box-shadow/-/css-box-shadow-1.0.0-3.tgz",
"integrity": "sha512-9jaqR6e7Ohds+aWwmhe6wILJ99xYQbfmK9QQB9CcMjDbTxPZjwEmUQpU91OG05Xgm8BahT5fW+svbsQGjS/zPg=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",

View File

@@ -18,6 +18,7 @@
"bcrypt": "^1.0.3",
"bower": "^1.7.9",
"core-js": "^2.6.2",
"css-box-shadow": "^1.0.0-3",
"fibers": "^2.0.2",
"meteor-node-stubs": "^0.3.3",
"qrcode": "^1.3.3",