<script setup>
import Logo from "../assets/svgs/Logo";
import Menu from "../assets/svgs/Menu";
const hover = Boolean
let boxTop
if (hover === true) {
boxTop = 'boxTop--0'
} else {
boxTop = 'boxTop--20'
}
</script>
<template>
<nav class=" w-full fixed flex items-center">
<div class="w-full flex h-full">
<div class="px-6 flex items-center justify-center h-full relative "
#mouseover="hover = true" #mouseleave="hover = false"
>
<Menu class="w-10 fill-white"/>
<div class="absolute w-full h-full bg-black" :style="boxTop"></div>
</div>
</div>
</nav>
</template>
I want to make it so when the mouse enters the div, the css class "boxTop--0" gets added, and, when the mouse leaves the div, the css class "boxTop--20" gets added. However, I can't figure out how to do it with compositon api.
I would be very greatful for any help.
Please take a look at following snippet:
You can create reactive state (boxTop) with ref and create function for changing state.
const { ref } = Vue
const app = Vue.createApp({
el: "#demo",
setup() {
const boxTop = ref(null)
const hover = (val) => {
if (val) {
boxTop.value = 'boxTop--0'
} else {
boxTop.value = 'boxTop--20'
}
}
return { boxTop, hover }
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3"></script>
<div id="demo">
<nav class=" w-full fixed flex items-center">
<div class="w-full flex h-full">
<div class="px-6 flex items-center justify-center h-full relative "
#mouseenter="hover(true)" #mouseleave="hover(false)"
>
hover me
<div class="absolute w-full h-full bg-black" :style="boxTop">
{{ boxTop }}
</div>
</div>
</div>
</nav>
</div>
Something like this should do the trick, using a computed.
<script setup>
import Menu from "../assets/svgs/Menu"
const hover = ref(true)
const boxTop = computed(() => hover.value ? 'boxTop--0' : 'boxTop--20')
</script>
<template>
<nav class="fixed flex items-center w-full">
<div class="flex w-full h-full">
<div class="relative flex items-center justify-center h-24 h-full px-6 bg-green-500" #mouseover="hover = true" #mouseleave="hover = false">
<Menu class="w-10 fill-white" />
<div class="absolute w-full h-full bg-black" :class="boxTop"></div>
</div>
</div>
</nav>
</template>
<style scoped>
.boxTop--0 {
background-color: red;
}
.boxTop--20 {
background-color: teal;
}
</style>
I've used :class="boxTop" and some classes to help me debug what you have tried.
Related
I'm new to swipe actions and furthermore dealing with these kinds of mobile-style gestures in the browser. I'm currently using a composable from the vueuse library called useSwipe to create a kind of 'swipe right to reveal buttons, swipe left to hide buttons' thing.
Below you'll see code that works for swiping right but can't figure out how to swipe back left. For a quick idea of the goal I'm trying to achieve check this demo out from a vue2 swiper library -> demo of a library that achieves desired result
<script setup>
const target = ref(null)
const container = ref(null)
const containerWidth = computed(() => container.value?.offsetWidth)
const left = ref('0')
const { isSwiping, lengthX } = useSwipe(target, {
passive: false,
onSwipe() {
if (containerWidth.value) {
if (lengthX.value < 0) {
const length = Math.abs(lengthX.value)
left.value = `${length}px`
} else {
left.value = '0'
}
}
},
onSwipeEnd() {
if (
lengthX.value < 0 &&
containerWidth.value &&
Math.abs(lengthX.value) / containerWidth.value >= 0.5
) {
left.value = '50%'
} else {
left.value = '0'
}
}
})
</script>
<template>
<div class="relative flex items-center h-full w-full overflow-hidden p-10 border-b border-b-neutral-200" ref="container">
// here's the two buttons that will be shown when swiping right
<div class="flex h-full absolute top-0 left-0">
<div class="flex justify-center items-center space-x-2 bg-alert w-36 py-7 px-3">
<p class="uppercase text-white">delete</p>
<i-outline-trash class="text-white"></i-outline-trash>
</div>
<div class="flex justify-center items-center space-x-2 bg-warn w-36 py-7 px-3">
<p class="uppercase text-white">edit</p>
<i-outline-pencil-alt class="text-white"></i-outline-pencil-alt>
</div>
</div>
// the part to be swiped right then left
<div class="flex w-full h-full bg-white space-x-5 absolute p-5 top-0 left-0 z-20" ref="target" :class="{ "transition-all ease-in-out duration-500": !isSwiping }" :style="{ left }">
<div class="space-y-2">
<div class="w-[6px] h-[6px] rounded-full bg-neutral-200"></div>
<div class="w-[6px] h-[6px] rounded-full bg-neutral-200"></div>
<div class="w-[6px] h-[6px] rounded-full bg-neutral-200"></div>
</div>
<div>1</div>
<div class="w-full h-full">
<div class="flex justify-between mb-1">
<h3>item<span class="ml-2 text-neutral-contrast-light">(variant)</span></h3>
<p>{{ itemPrice }}</p>
</div>
<div class="flex justify-between text-xs">
<p>+ name</p>
<p>price</p>
</div>
</div>
</div>
</div>
</template>
I tried adding a simple rectangle on top of a canvas using Fabric.js in Vue 3
I was able to load rectangle on top of canvas inside a page, where as it doesn't seem to work inside a component.
I tried putting a 2 second timeout as well in component, but it doesn't render anything.
Is there any workaround or am I making any mistake?
<template>
<TransitionRoot as="template" :show="showCanvas">
<Dialog as="div" class="fixed z-10 inset-0" #close="showCanvas = false">
<div class="w-full" style="width: 60%!important; height:auto">
<TransitionChild as="template" enter="ease-out duration-300" enter-from="opacity-0"
enter-to="opacity-100" leave="ease-in duration-200" leave-from="opacity-100" leave-to="opacity-0">
<DialogOverlay class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</TransitionChild>
<!-- This element is to trick the browser into centering the modal contents. -->
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true"></span>
<TransitionChild as="template" enter="ease-out duration-300"
enter-from="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
enter-to="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200"
leave-from="opacity-100 translate-y-0 sm:scale-100"
leave-to="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
<div
class="bg-white">
<div>
<div class="mt-3 text-center sm:mt-5">
<!-- This example requires Tailwind CSS v2.0+ -->
<div class="fixed inset-0 overflow-y-auto z-50 flex justify-center items-center">
<div class=" bg-white m-auto rounded-lg shadow-md" role="dialog" aria-modal="true"
aria-labelledby="modal- headline" style="width: 550px !important;height:auto;">
<canvas id="canvas" ></canvas>
</div>
</div>
<div class="flex w-full mx-auto absolute bottom-56 justify-center z-50">
<button type="button"
class="w-50 mr-2 inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-indigo-600 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:col-start-2 sm:text-sm"
>
Save
</button>
<button type="button"
class="mt-3 w-50 ml-2 inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:col-start-1 sm:text-sm"
#click="closePopup" ref="cancelButtonRef">
Cancel
</button>
</div>
</div>
</div>
</div>
</TransitionChild>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { fabric } from 'fabric'
import { ref, watch, reactive, onMounted } from 'vue';
import { Dialog, DialogOverlay, DialogTitle, TransitionChild, TransitionRoot } from '#headlessui/vue'
export default {
components:
{
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
},
props: {
showCanvas: {
required: true,
type: Boolean
},
},
setup(props, context) {
reactive(props.showCanvas) // Make the prop reactive so that we can watch for changes
let canvas = undefined
watch(() => props.showCanvas, (selection, prevSelection) => {
if (selection == true) {
bindImage()
}
})
function bindImage()
{
canvas = new fabric.Canvas('canvas');
setTimeout(function(){
var rectangle = new fabric.Rect({
width: 300,
height: 200,
fill: '',
stroke: 'green',
strokeWidth: 5
});
console.log("Rectangle>>>>", rectangle);
// Render the Rect in canvas
canvas.add(rectangle);
}, 2000);
}
function closePopup()
{
context.emit("closeCanvas")
}
return {
closePopup,
}
}
}
</script>
<style>
</style>
You should put them into the onMounted block, for example:
<template>
<canvas width="600" height="600" id="canvas" style="border: 1px solid #ccc;"></canvas>
</template>
<script setup>
import { onMounted } from 'vue'
import { fabric } from 'fabric'
function init() {
let canvas = new fabric.Canvas('canvas') // 实例化fabric,并绑定到canvas元素上
// 圆
let circle = new fabric.Circle({
left: 100,
top: 100,
radius: 50,
})
canvas.add(circle)
}
onMounted(() => {
init()
})
</script>
I am developing a Vue project with Vuex and Vue 3.
I have a serious problem with my code.
<template>
<div class="px-2 pt-3 text-left">
<div class="flex justify-between items-center">
<h3 class="text-red-700 font-bold text-sm">Texts</h3>
</div>
<div class="mt-4">
<p class="text-sm">Title</p>
<div class="flex items-center border">
<input
v-model.lazy="introSlide.title"
class="
rounded
mb-0
py-2
px-2
focus:outline-none
text-sm
w-full
text-grey-darker
"
placeholder="Enter Title"
/>
</div>
</div>
<div class="mt-4">
<p class="text-sm">Subtitle</p>
<div class="flex items-center border">
<textarea
v-model.lazy="introSlide.subTitle"
class="
rounded
mb-0
py-2
px-2
focus:outline-none
text-sm
w-full
text-grey-darker
min-h-max
h-32
"
placeholder="Presenter Information"
/>
</div>
</div>
<hr />
<div class="flex justify-between items-center mt-6">
<h3 class="text-red-700 font-bold text-sm">Images</h3>
</div>
<div class="mt-4">
<p class="text-sm">Company Logo</p>
<div class="flex items-center border">
<label
class="
flex flex-col
items-center
rounded-lg
tracking-wide
cursor-pointer
border-dashed border-2
w-40
"
:class="introSlide.logo ? '' : 'px-14 py-6'"
>
<input
id="logo_upload"
ref="imgInput"
class="hidden"
type="file"
accept="image/*"
name="logo_upload"
#input="pickFile($event.target.files)"
/>
<img
v-show="introSlide.logo"
:src="`${introSlide.logo}`"
class="rounded-lg w-40 h-24 object-cover"
/>
</label>
<button #click="presentations">testest</button>
</div>
</div>
</div>
</template>
<script>
import introSlideDefault from '#/data/slideContent/introSlide.js'
import { mapMutations } from 'vuex'
import { useStore } from 'vuex'
import { useRoute } from 'vue-router'
import { ref, watch, computed } from 'vue'
export default {
setup() {
const store = useStore()
const route = useRoute()
let draftPayload = {
presentationId: route.params.presentationId,
prePopulatedSlide: route.params.slideId,
}
const slideContent = computed(() =>
store.getters['rfps/getslideContentBySlideId'](draftPayload),
)
let introSlide = ref(JSON.parse(JSON.stringify(introSlideDefault)))
const introSlide = computed(() => {
if (slideContent.value == null) {
let introSlideTest = ref({
title: '',
subTitle: '',
logo: '',
})
return introSlideTest
} else {
let draftContent = ref(JSON.parse(slideContent.value.draftSlideContent))
let introSlideTest = {
title: draftContent.value.title,
subTitle: draftContent.value.subTitle,
logo: draftContent.value.logo,
}
return introSlideTest
}
)
watch(
() => introSlide,
(introSlide) => {
store.commit('rfps/setIntroSlide', introSlide.value)
let updateDraftPayload = {
content: JSON.stringify(introSlide.value),
presentationId: route.params.presentationId,
slideId: slideContent.value.slideId,
version: slideContent.value.version,
}
store.dispatch('rfps/updateDraft', updateDraftPayload)
},
{ deep: true },
{ immediate: false },
)
return {
introSlide,
slideContent,
}
},
</script>
If I watch any ref value, For example:
let introSlide = ref({
title: '',
subTitle: '',
logo: '',
})
I can watch for changes with v-model, but if I use a computed value I can't watch it.
I need to get data with getter and to watch it's changes with v-model
you need to be more specify to get the value
const slideContent = computed(() =>
store.getters['rfps/getslideContentBySlideId'](draftPayload).specifyAttribute,
)
I have a simple Vue3 application that is making heavy use of TailwindUI components. I'm trying to place a TinyMce editor inside of a slide over component and that works fine. The issue is the entry animation.
On first entry, it slides in like it's supposed to. However, if it is closed and reopened the entry animation is gone. The whole time the exit animation continues to work without issue. Is there a way I can do this and keep the animation intact?
Here is a CodeSandBox with the issue reproduced in it's simplest form.
Here is the relevant code:
App.vue
<template>
<button #click="open = true">Open Menu</button>
<SlideOver :open="open" #close="open = false" />
</template>
<script>
import { ref } from "vue";
import SlideOver from "./components/slide-over.vue";
export default {
name: "App",
components: {
SlideOver,
},
setup() {
const open = ref(false);
return { open };
},
};
</script>
slide-over.vue
<!-- This example requires Tailwind CSS v2.0+ -->
<template>
<TransitionRoot as="template" :show="open">
<Dialog
as="div"
static
class="fixed inset-0 overflow-hidden"
#close="$emit('close')"
:open="open"
>
<div class="absolute inset-0 overflow-hidden">
<DialogOverlay class="absolute inset-0" />
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<TransitionChild
as="template"
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter-from="translate-x-full"
enter-to="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave-from="translate-x-0"
leave-to="translate-x-full"
>
<div class="w-screen max-w-md">
<div
class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"
>
<div class="px-4 sm:px-6">
<div class="flex items-start justify-between">
<DialogTitle class="text-lg font-medium text-gray-900">
Panel title
</DialogTitle>
<div class="ml-3 h-7 flex items-center">
<button
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
#click="$emit('close')"
>
<span class="sr-only">Close panel</span>
<XIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div class="mt-6 relative flex-1 px-4 sm:px-6">
<TinyMceEditor api-key="no-api-key" />
</div>
</div>
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
} from "#headlessui/vue";
import { XIcon } from "#heroicons/vue/outline";
import TinyMceEditor from "#tinymce/tinymce-vue";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
XIcon,
TinyMceEditor,
},
props: {
open: {
type: Boolean,
default: false,
},
},
setup() {},
};
</script>
In my opinion, this is a problem with loading the TinyMce Editor (I don't know exactly what the problem is). I added a delay in loading the editor after opening the modal using watchEffect based on the props open with setTimeout in it and v-if on the TinyMceEditor tag. It may not be a perfect and aesthetic solution, but the animation works smoothly.
Here is a code in codesandbox.io.
And code here: slide-over.vue (App.vue stays the same)
<template>
<TransitionRoot as="template" :show="open">
<Dialog
as="div"
static
class="fixed inset-0 overflow-hidden"
#close="$emit('close')"
:open="open"
>
<div class="absolute inset-0 overflow-hidden">
<DialogOverlay class="absolute inset-0" />
<div class="fixed inset-y-0 right-0 pl-10 max-w-full flex">
<TransitionChild
as="template"
enter="transform transition ease-in-out duration-500 sm:duration-700"
enter-from="translate-x-full"
enter-to="translate-x-0"
leave="transform transition ease-in-out duration-500 sm:duration-700"
leave-from="translate-x-0"
leave-to="translate-x-full"
>
<div class="w-screen max-w-md">
<div
class="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll"
>
<div class="px-4 sm:px-6">
<div class="flex items-start justify-between">
<DialogTitle class="text-lg font-medium text-gray-900">
Panel title
</DialogTitle>
<div class="ml-3 h-7 flex items-center">
<button
class="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
#click="$emit('close')"
>
<span class="sr-only">Close panel</span>
<XIcon class="h-6 w-6" aria-hidden="true" />
</button>
</div>
</div>
</div>
<div class="mt-6 relative flex-1 px-4 sm:px-6">
<TinyMceEditor v-if="loadEditor" api-key="no-api-key" />
</div>
</div>
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script>
import { ref, watchEffect } from "vue";
import {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
} from "#headlessui/vue";
import { XIcon } from "#heroicons/vue/outline";
import TinyMceEditor from "#tinymce/tinymce-vue";
export default {
components: {
Dialog,
DialogOverlay,
DialogTitle,
TransitionChild,
TransitionRoot,
XIcon,
TinyMceEditor,
},
props: {
open: {
type: Boolean,
default: false,
},
},
setup(props) {
const loadEditor = ref(false);
watchEffect(() => {
if (props.open) {
setTimeout(function () {
loadEditor.value = true;
}, 10);
} else {
loadEditor.value = false;
}
});
return { loadEditor };
}
};
</script>
I'm using this code
var ComponentClass = Vue.extend(Notification);
var instance = new ComponentClass({
propsData: {
notification: notification
}
});
instance.$mount();
this.$refs.notificationContainer.appendChild(instance.$el);
to programmatically create a component when a new notification is received through pusher, this is my notification component
<template>
<transition
enter-active-class="transition ease-out duration-100 transform"
enter-class="opacity-0 scale-95"
enter-to-class="opacity-100 scale-100"
leave-active-class="transition ease-in duration-75 transform"
leave-class="opacity-100 scale-100"
leave-to-class="opacity-0 scale-95"
>
<div class="bg-gray-100 px-4 py-2 border-0 rounded relative mb-4 text-gray-700 flex items-center">
<div class="flex-shrink-0 h-10 w-10">
<img
class="h-10 w-10 rounded-full"
:src="notification.user.profile_photo_url"
:alt="notification.user.username"
>
</div>
<span class="inline-block align-middle flex-1 ml-2 mr-4">
<strong>{{notification.user.username}}</strong> {{notification.message}}
</span>
<button class="focus:outline-none">
<i
class="fad fa-times text-red-600"
></i>
</button>
</div>
</transition>
</template>
<script>
export default {
props: {
notification: Object
}
}
</script>
but for some reason the transition doesnt run when the component is created
Fixed by adding appear to the transition element