Skip to content

Commit daa0f8b

Browse files
authored
feat(morph): add morph function and directive for DOM elements/components quasarframework#6292 (quasarframework#7466)
1 parent e1128a3 commit daa0f8b

File tree

26 files changed

+2959
-3
lines changed

26 files changed

+2959
-3
lines changed

docs/src/assets/menu.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -705,6 +705,10 @@ const directives = [
705705
name: 'Mutation',
706706
path: 'mutation'
707707
},
708+
{
709+
name: 'Morph',
710+
path: 'morph'
711+
},
708712
{
709713
name: 'Scroll',
710714
path: 'scroll'
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<template>
2+
<div class="q-pa-md relative-position" style="min-height: 300px">
3+
<div class="absolute-bottom-right q-ma-lg">
4+
<div ref="refFab" class="absolute-center bg-accent" style="border-radius: 50%; width: 50%; height: 50%" />
5+
6+
<q-fab
7+
direction="up"
8+
icon="add"
9+
color="accent"
10+
@input="val => val === true && morph(false)"
11+
>
12+
<q-fab-action color="primary" @click="morph(true)" icon="alarm" />
13+
</q-fab>
14+
</div>
15+
16+
<q-card
17+
v-if="toggle"
18+
ref="refCard"
19+
class="my-card text-white absolute-center bg-grey-10"
20+
@click="morph(false)"
21+
>
22+
<q-card-section>
23+
<div class="text-h6">Our Changing Planet</div>
24+
<div class="text-subtitle2">by John Doe</div>
25+
</q-card-section>
26+
27+
<q-card-section class="q-pt-none">
28+
{{ lorem }}
29+
</q-card-section>
30+
</q-card>
31+
</div>
32+
</template>
33+
34+
<script>
35+
import { dom } from 'quasar'
36+
37+
const { morph } = dom
38+
39+
export default {
40+
data () {
41+
return {
42+
toggle: false,
43+
44+
lorem: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'
45+
}
46+
},
47+
48+
methods: {
49+
morph (state) {
50+
if (state !== this.toggle) {
51+
const getFab = () => this.$refs.refFab
52+
const getCard = () => this.$refs.refCard ? this.$refs.refCard.$el : void 0
53+
54+
morph(
55+
{
56+
from: this.toggle === true ? getCard : getFab,
57+
to: this.toggle === true ? getFab : getCard
58+
},
59+
() => {
60+
this.toggle = state
61+
},
62+
500
63+
)
64+
}
65+
}
66+
}
67+
}
68+
</script>
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<template>
2+
<div class="q-pa-md">
3+
<div
4+
class="fixed-full image-gallery__blinder bg-orange"
5+
:class="indexZoomed !== void 0 ? 'image-gallery__blinder--active' : void 0"
6+
@click="zoomImage()"
7+
/>
8+
9+
<div
10+
class="row justify-center q-gutter-sm q-mx-auto scroll relative-position"
11+
style="max-width: 80vw; max-height: 80vh; z-index: 1"
12+
>
13+
<q-img
14+
v-for="(src, index) in images"
15+
:key="index"
16+
ref="refThumb"
17+
class="image-gallery__image"
18+
:style="index === indexZoomed ? 'opacity: 0.3' : void 0"
19+
:src="src"
20+
@click="zoomImage(index)"
21+
/>
22+
</div>
23+
24+
<q-img
25+
ref="refFull"
26+
class="image-gallery__image image-gallery__image-full fixed-center"
27+
:class="indexZoomed !== void 0 ? 'image-gallery__image-full--active' : void 0"
28+
:src="images[indexZoomed]"
29+
@load="imgLoadedResolve"
30+
@error="imgLoadedReject"
31+
@click="zoomImage()"
32+
/>
33+
</div>
34+
</template>
35+
36+
<style lang="sass">
37+
.image-gallery
38+
&__image
39+
border-radius: 3%/5%
40+
width: 150px
41+
max-width: 20vw
42+
cursor: pointer
43+
z-index: 2
44+
45+
&-full
46+
width: 800px
47+
max-width: 70vw
48+
z-index: 1000
49+
pointer-events: none
50+
51+
&--active
52+
pointer-events: all
53+
&__blinder
54+
opacity: 0
55+
z-index: 0
56+
pointer-events: none
57+
transition: opacity 0.3s ease-in-out
58+
59+
&--active
60+
opacity: 0.2
61+
pointer-events: all
62+
</style>
63+
64+
<script>
65+
import { dom } from 'quasar'
66+
67+
const { morph } = dom
68+
69+
export default {
70+
data () {
71+
return {
72+
indexZoomed: void 0,
73+
imgLoaded: {
74+
promise: Promise.resolve(),
75+
resolve: () => {},
76+
reject: () => {}
77+
},
78+
images: Array(24).fill(null).map((_, i) => 'https://picsum.photos/id/' + i + '/500/300')
79+
}
80+
},
81+
82+
methods: {
83+
imgLoadedResolve () {
84+
this.imgLoaded.resolve()
85+
},
86+
87+
imgLoadedReject () {
88+
this.imgLoaded.reject()
89+
},
90+
91+
zoomImage (index) {
92+
const { indexZoomed } = this
93+
94+
this.imgLoaded.reject()
95+
96+
const zoom = () => {
97+
if (index !== void 0 && index !== indexZoomed) {
98+
this.imgLoaded.promise = new Promise((resolve, reject) => {
99+
this.imgLoaded.resolve = () => {
100+
this.imgLoaded.resolve = () => {}
101+
this.imgLoaded.reject = () => {}
102+
103+
resolve()
104+
}
105+
this.imgLoaded.reject = () => {
106+
this.imgLoaded.resolve = () => {}
107+
this.imgLoaded.reject = () => {}
108+
109+
reject()
110+
}
111+
})
112+
113+
this.cancel = morph(
114+
{
115+
from: this.$refs.refThumb[index].$el,
116+
to: this.$refs.refFull.$el
117+
},
118+
() => {
119+
this.indexZoomed = index
120+
},
121+
{
122+
waitFor: this.imgLoaded.promise,
123+
duration: 400,
124+
hideFromClone: true,
125+
style: 'z-index: 1',
126+
onReady: end => {
127+
if (end === 'from' && this.indexZoomed === index) {
128+
this.indexZoomed = void 0
129+
}
130+
}
131+
}
132+
)
133+
}
134+
}
135+
136+
if (
137+
indexZoomed !== void 0 &&
138+
(this.cancel === void 0 || this.cancel() === false)
139+
) {
140+
morph(
141+
{
142+
from: this.$refs.refFull.$el,
143+
to: this.$refs.refThumb[indexZoomed].$el
144+
},
145+
() => {
146+
this.indexZoomed = void 0
147+
},
148+
{
149+
duration: 200,
150+
keepToClone: true,
151+
style: 'z-index: 1',
152+
onReady: zoom
153+
}
154+
)
155+
}
156+
else {
157+
zoom()
158+
}
159+
}
160+
}
161+
}
162+
</script>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<template>
2+
<div class="q-pa-md">
3+
<div class="row no-wrap q-gutter-x-sm" style="overflow-x: auto; overflow-y: visible;">
4+
<q-img
5+
v-for="(src, index) in images"
6+
:key="index"
7+
ref="refThumb"
8+
class="cursor-pointer"
9+
:class="index === indexZoomed ? 'fixed-top q-mt-md q-mx-auto z-top' : void 0"
10+
style="border-radius: 3%/5%; flex: 0 0 10vw"
11+
:style="index === indexZoomed ? 'width: 800px; max-width: 70vw' : void 0"
12+
:src="src"
13+
@click="zoomImage(index)"
14+
/>
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script>
20+
import { dom } from 'quasar'
21+
22+
const { morph } = dom
23+
24+
export default {
25+
data () {
26+
return {
27+
indexZoomed: void 0,
28+
images: Array(24).fill(null).map((_, i) => 'https://picsum.photos/id/' + i + '/500/300')
29+
}
30+
},
31+
32+
methods: {
33+
zoomImage (index) {
34+
const { indexZoomed } = this
35+
36+
this.indexZoomed = void 0
37+
38+
if (index !== void 0 && index !== indexZoomed) {
39+
this.cancel = morph(
40+
this.$refs.refThumb[index].$el,
41+
() => {
42+
this.indexZoomed = index
43+
},
44+
{
45+
duration: 500,
46+
style: 'z-index: 1',
47+
onReady: end => {
48+
if (end === 'from' && this.indexZoomed === index) {
49+
this.indexZoomed = void 0
50+
}
51+
}
52+
}
53+
)
54+
}
55+
56+
if (
57+
indexZoomed !== void 0 &&
58+
(this.cancel === void 0 || this.cancel() === false)
59+
) {
60+
morph(
61+
this.$refs.refThumb[indexZoomed].$el,
62+
void 0,
63+
{
64+
waitFor: 100,
65+
duration: 300,
66+
style: 'z-index: 1'
67+
}
68+
)
69+
}
70+
}
71+
}
72+
}
73+
</script>
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<template>
2+
<div class="q-pa-md">
3+
<div class="q-gutter-y-sm" style="overflow-x: visible; overflow-y: auto; width: 300px; max-width: 20vw; max-height: 80vh">
4+
<q-img
5+
v-for="(src, index) in images"
6+
:key="index"
7+
ref="refThumb"
8+
class="cursor-pointer"
9+
:class="index === indexZoomed ? 'fixed-top-right q-mr-md q-mt-md z-top' : void 0"
10+
style="border-radius: 3%/5%;"
11+
:style="index === indexZoomed ? 'width: 800px; max-width: 70vw;' : void 0"
12+
:src="src"
13+
@click="zoomImage(index)"
14+
/>
15+
</div>
16+
</div>
17+
</template>
18+
19+
<script>
20+
import { dom } from 'quasar'
21+
22+
const { morph } = dom
23+
24+
export default {
25+
data () {
26+
return {
27+
indexZoomed: void 0,
28+
images: Array(24).fill(null).map((_, i) => 'https://picsum.photos/id/' + i + '/500/300')
29+
}
30+
},
31+
32+
methods: {
33+
zoomImage (index) {
34+
const { indexZoomed } = this
35+
36+
this.indexZoomed = void 0
37+
38+
if (index !== void 0 && index !== indexZoomed) {
39+
this.cancel = morph(
40+
this.$refs.refThumb[index].$el,
41+
() => {
42+
this.indexZoomed = index
43+
},
44+
{
45+
duration: 500,
46+
style: 'z-index: 1',
47+
onReady: end => {
48+
if (end === 'from' && this.indexZoomed === index) {
49+
this.indexZoomed = void 0
50+
}
51+
}
52+
}
53+
)
54+
}
55+
56+
if (
57+
indexZoomed !== void 0 &&
58+
(this.cancel === void 0 || this.cancel() === false)
59+
) {
60+
morph(
61+
this.$refs.refThumb[indexZoomed].$el,
62+
void 0,
63+
{
64+
waitFor: 100,
65+
duration: 300,
66+
style: 'z-index: 1'
67+
}
68+
)
69+
}
70+
}
71+
}
72+
}
73+
</script>

0 commit comments

Comments
 (0)