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

@@ -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
//);
};