英文:
Vuetify: how to bounce an image
问题
I have a project which uses v-chip
components and when I move them around and drop them elsewhere, I have used a 'bounce' transition to animate them:
<Transition name="bounce">
<v-chip ...
And the CSS:
.bounce-enter-active {
animation: bounce-in 0.5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
This works well, but if I try the same technique with a v-img
, there is no bounce. So, how do I bounce an image?
Later edit
@Moritz's answer below was very helpful, improved my 'bounce' and is a great answer to the question I originally posed.
However, it was @yoduh's comment that led me to the key problem. I had changed the way the images were being moved, so in effect the reactive v-img
didn't get a look-in to the DOM change. So I now have simply:
<v-chip class="bounce-enter-active" ...
instead of:
<Transition name="bounce">
<v-chip ...
Maybe I should have added this as an answer, but it doesn't actually answer my original question!
英文:
I have a project which uses v-chip
components and when I move them around and drop them elsewhere, I have used a 'bounce' transition to animate them:
<Transition name="bounce">
<v-chip ......
and the css:
.bounce-enter-active {
animation: bounce-in 0.5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
This works well, but if I try the same technique with a v-img
there is no bounce. So, how do I bounce an image?
Later edit
@Moritz 's answer below was very helpful, improved my 'bounce' and is a great answer to the question I originally posed.
However, it was @yoduh 's comment that led me to the key problem. I had changed the way the images were being moved, so in effect the reactive v-img
didn't get a look-in to the DOM change. So I now have simply:
<v-chip class="bounce-enter-active" ......
instead of:
<Transition name="bounce">
<v-chip ......
Maybe I should have added this as an answer, but it doesn't actually answer my original question!
答案1
得分: 1
大多数情况下,您看不到动画是因为它发生在图像加载的同时。请查看以下代码片段以查看差异:
const { createApp, ref } = Vue;
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = {
setup() {
const isOn = ref(false)
const loadUncached = ref(false)
const showPreloaded = ref(false)
const nonEmpty = ref(false)
const rawSrc = 'https://www.neonpoodle.co.uk/cdn/shop/products/HeadInCloudsPink_620x.jpg?v=1554576513'
const src = ref(rawSrc)
const loadSrc = () => {
const src = rawSrc + (loadUncached.value ? '?asfd=' + Math.random() : '')
return !showPreloaded.value ? src : new Promise(resolve => {
const img = new Image()
img.onload = () => resolve(src)
img.src = src
})
}
setInterval(async () => {
const doShow = !isOn.value
if (doShow) {
src.value = await loadSrc()
}
isOn.value = doShow
}, 2000)
return {
isOn,
loadUncached,
showPreloaded,
nonEmpty,
src
}
}
}
createApp(app).use(vuetify).mount('#app')
如果要查看完整的动画,请确保在显示图像之前将图像加载到缓存中。一种简单的方法是将URL放入“Image”中,并等待“onloaded”事件,此时图像已加载到缓存中:
const preloadSrc = (src) => {
return new Promise(resolve => {
const img = new Image()
img.onload = () => resolve(true)
img.src = src
})
}
showVImg.value = await preloadSrc('/the/image/url')
还有一个@load
事件,但要将其与<Transition>
和v-if
的组合一起使用可能会很麻烦。但是,由于您正在使用真正的CSS动画,您可以在动画完成后使用它(如果在动画完成后保留类名可接受的话):
const { createApp, ref } = Vue;
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = {
setup() {
const rawSrc = 'https://www.quilkin.co.uk/shared/topright.png'
const src = ref(null)
const loaded = ref(false)
setInterval(async () => {
src.value = src.value ? null : rawSrc + '?asfd=' + Math.random()
loaded.value = false
}, 2000)
return {src, loaded}
}
}
createApp(app).use(vuetify).mount('#app')
此外,您还可以在v-img
上设置背景颜色和固定的高度和宽度,这样在图像加载时会看到背景动画。
最后,为了确保动画使用图像而不是缩小的版本,请在CSS中将image-rendering
设置为crisp-edges
。似乎只需要在动画的0%关键帧上设置它:
@keyframes bounce-in {
0% {
transform: scale(0);
image-rendering: crisp-edges;
}
...
}
英文:
Most likely, you don't see the animation because it happens while the image still loads. Have a look at the snippet to see the difference:
<!-- begin snippet: js hide: true console: false babel: false -->
<!-- language: lang-js -->
const { createApp, ref } = Vue;
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = {
setup() {
const isOn = ref(false)
const loadUncached = ref(false)
const showPreloaded = ref(false)
const nonEmpty = ref(false)
const rawSrc = 'https://www.neonpoodle.co.uk/cdn/shop/products/HeadInCloudsPink_620x.jpg?v=1554576513'
const src = ref(rawSrc)
const loadSrc = () => {
const src = rawSrc + (loadUncached.value ? '?asfd=' + Math.random() : '')
return !showPreloaded.value ? src : new Promise(resolve => {
const img = new Image()
img.onload = () => resolve(src)
img.src = src
})
}
setInterval(async() => {
const doShow = !isOn.value
if (doShow) {
src.value = await loadSrc()
}
isOn.value = doShow
}, 2000)
return {
isOn,
loadUncached,
showPreloaded,
nonEmpty,
src
}
}
}
createApp(app).use(vuetify).mount('#app')
<!-- language: lang-css -->
.bounce-enter-active {
animation: bounce-in 0.5s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
image-rendering: crisp-edges;
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
.non-empty{
width: 100px;
height: 100px;
background: purple;
}
<!-- language: lang-html -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.css" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-main class="ma-4">
<div class="d-flex">
<v-switch v-model="loadUncached" label="load uncached (leads to stunted transition)"></v-switch>
<v-switch v-model="showPreloaded" label="preload image (restores transition)" :disabled="!loadUncached"></v-switch>
<v-switch v-model="nonEmpty" label="set background"></v-switch>
</div>
<Transition name="bounce">
<v-img v-if="isOn" :src="src" width="100" :class="{'non-empty' : nonEmpty}"></v-img>
</Transition>
</v-main>
</v-app>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.js"></script>
<!-- end snippet -->
If you want to see the full animation, make sure that the image is loaded into cache before you show it. A simple way to do it is to put the url into an Image
and wait for the onloaded
event, at which point the image is loaded into cache:
const preloadSrc = (src) => {
return new Promise(resolve => {
const img = new Image()
img.onload = () => resolve(true)
img.src = src
})
}
showVImg.value = await preloadSrc('/the/image/url')
There is also a @load
event on v-img
, but it is quite annoying to get it to work with a combination of <Transition>
and v-if
. But since you are using a real CSS animation, you could use it without <Transition>
(if it is acceptable to leave the class after the animation finished):
<!-- begin snippet: js hide: true console: false babel: false -->
<!-- language: lang-js -->
const { createApp, ref } = Vue;
const { createVuetify } = Vuetify
const vuetify = createVuetify()
const app = {
setup() {
const rawSrc = 'https://www.quilkin.co.uk/shared/topright.png'
const src = ref(null)
const loaded = ref(false)
setInterval(async() => {
src.value = src.value ? null : rawSrc + '?asfd=' + Math.random()
loaded.value = false
}, 2000)
return {src, loaded}
}
}
createApp(app).use(vuetify).mount('#app')
<!-- language: lang-css -->
.bounce {
animation: bounce-in 1s;
}
@keyframes bounce-in {
0% {
transform: scale(0);
image-rendering: crisp-edges;
}
50% {
transform: scale(1.25);
}
100% {
transform: scale(1);
}
}
<!-- language: lang-html -->
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.css" />
<link href="https://cdn.jsdelivr.net/npm/@mdi/font@5.x/css/materialdesignicons.min.css" rel="stylesheet">
<div id="app">
<v-app>
<v-main class="ma-4">
<v-img
v-if="src"
:src="src"
width="100"
:class="{'bounce' : loaded}"
@load="loaded = true"
></v-img>
</v-main>
</v-app>
</div>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify@3/dist/vuetify.min.js"></script>
<!-- end snippet -->
Alternatively, you could also set a background color and fixed height and width on the v-img
, then you see that background animated while the image loads.
Finally, to make sure the animation uses the image and not a scaled-down version, set image-rendering
to crisp-edges
in your CSS. It seems to be enough to set it on the 0% keyframe of the animation:
@keyframes bounce-in {
0% {
transform: scale(0);
image-rendering: crisp-edges;
}
...
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论