Vue3 - possible to bind prop to background-image in style? - vue.js

Say I have the following component:
<template>
<q-layout view="lHh lpR lFf">
<q-page-container>
<div class="image-container">
Image Container
</div>
<slot />
</q-page-container>
</q-layout>
</template>
<script lang='ts' setup>
const props = defineProps({
image: { type: String, required: true, default: '' },
});
// console.log(image);
</script>
<style lang="scss" scoped>
.image-container {
display: none;
#media (min-width: $breakpoint-sm-min) {
background: v-bind("props.image") no-repeat left center fixed;
display: block;
background-size: cover;
height: 100%;
width: auto;
}
}
</style>
I can verify that the prop comes through as a string correctly, and according to the docs I can use v-bind to more easily insert dynamic values to the styles.
The image doesn't show up though, and I see this when I open the inspector:
I've also tried variations of wrapping the v-bind inside of url(), but nothing appears to be working. Is this just not yet possible with Vue3?
Note: I am currently using Vue v3.2.29.

Thanks to #tony19 in the comments, we figured out that I wasn't too far off. Essentially, rather than trying to use url() in the css, I've moved that up to the parent that is passing down the prop.
Parent.vue
<template>
<Child :image="`url(${image})`">
...
</Child>
</template>
Child.vue
<template>
<q-layout view="lHh lpR lFf">
<q-page-container>
<div class="image-container">
Image Container
</div>
<slot />
</q-page-container>
</q-layout>
</template>
<script lang='ts' setup>
const props = defineProps({
image: { type: String, required: true, default: '' },
});
</script>
<style lang="scss" scoped>
.image-container {
display: none;
#media (min-width: $breakpoint-sm-min) {
background: v-bind("props.image") no-repeat left center fixed;
display: block;
background-size: cover;
height: 100%;
width: auto;
}
}
</style>

Related

How to pass props dynamically in vue

I am trying to pass props to a router-link which is used to take me to the update or delete page. In order for me to update the right element, I need to pass the item's id as a prop to the component(dropdown menu) to dynamically render the update and delete pages.
Here is my dropdown component:
<template>
<div class="dropdown">
<button #click="toggleMenu">
<fa class="dropdown_icon" icon="fa-solid fa-arrow-down" />
</button>
<div v-if="showMenu" class="menu">
<div class="menu-item" #click="itemClicked">
<router-link :to="`/updateList/${id}`" class="list__link"
>Update</router-link
>
<br />
<router-link :to="`/deleteList/${id}`" class="list__link"
>Delete</router-link
>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'DropdownList',
props: ['id'],
data() {
return {
showMenu: false,
};
},
methods: {
toggleMenu() {
this.showMenu = !this.showMenu;
},
itemClicked() {
this.toggleMenu();
},
},
};
</script>
<style scoped>
.dropdown_icon {
padding: 0.2rem;
color: #ffd700;
background: black;
margin-top: 15px;
transition: var(--transition);
border-radius: 0.2rem;
height: 17px;
}
.dropdown_icon:hover {
background: white;
color: black;
height: 17px;
}
.menu {
background: white;
padding-left: 2rem;
padding-right: 2rem;
border-radius: 1rem;
}
.list_plus {
padding: 0.5rem;
border: 1px solid gray;
border-radius: 1rem;
transition: var(--transition);
}
.list_plus:hover {
background: black;
color: #ffd700;
}
.createList {
text-decoration: none;
}
.list__link {
text-decoration: none;
color: black;
}
</style>
Here is my code for the part in which I am sending the element's id as a prop to the component:
div class="all__lists" v-for="(item, index) in items" :key="index">
<div class="list">
<div class="title">
<div class="title__options">
<h1 class="list_name">{{ item[0].list_name }}</h1>
<Dropdown :v-bind:id="`${item[0].list_id}`" />
<!-- V-menu -->
<!--menu ends-->
</div>
</div>
</div>
</div>
The Items object looks like:
But when I Try to access the update or delete page, the router redirects me to /updateList/undefined instead of /updateList/1 or something. Can anybody help me in fixing this?
Your problem is that you mixed the v-bind directive with its shorthand :.
You wrote :v-bind:id instead of v-bind:id (or :id).
With your code, you should be able to get the id by defining the props v-bind:id in the child. It will work since :v-bind:id will be converted as v-bind:v-bind:id.

Problems with scoped styles and deep styling in vue 3

Im having a lot of problems triying to style a child from parent in vue3.
In this case, i create a generic button with some css properties, and i try to customize this button from other component
Parent
<template>
<OwnButton
class="accept-button"
#ownButtonClicked="emit('accept')"
>
<slot>
ACCEPT
</slot>
</OwnButton>
</template>
<script setup>
import OwnButton from 'path/to/own-button.vue';
const emit = defineEmits(['accept']);
</script>
<style scoped>
.accept-button :deep(.own-button)
{
background-color : #4CAF50 !important;
outline-color : green !important;
}
.accept-button :deep(.own-button:hover)
{
background-color: green !important;
}
</style>
Child
<template>
<button
class="own-button"
type="button"
#click="emit('ownButtonClicked')"
v-on:keyup.enter="emit('ownButtonClicked')"
>
<slot>
</slot>
</button>
</template>
<script setup>
const emit = defineEmits
([
'ownButtonClicked'
]);
</script>
<style scoped>
.own-button
{
background-color : azure;
outline-color : lightblue;
color : black;
margin : 2px;
padding : 5px;
border-radius : 15px;
border : 0;
box-shadow : 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
outline-style : solid;
min-width : 100px;
max-width : 150px;
}
.own-button:hover
{
cursor: pointer;
}
</style>
I tried everything I could think of, including using :deep(button) in parent
This is a design limitation of Vue 3 when dealing with multi-root nodes
I wrote about this in my article Scoped styles and multi-root nodes don't work well together.
Understanding the issue
In Vue 3 we can finally have more than "one root node" components. That is great, but there is a design limitation when doing that. Imagine we have a child component:
<template>
<p class="my-p">First p</p>
<p class="my-p">Second p</p>
</template>
And a parent component:
<template>
<h1>My awesome component</h1>
<MyChildComponent />
</template>
<style scoped>
// There is no way to style the p tags of MyChildComponent
.my-p { color: red; }
:deep(.my-p) { color: red; }
</style>
There is no way from the scoped styling of the multi-root parent component to style the child component's p tags.
So in short, a multi-root component, can't target multi-root child component's styles with scoped styles.
Solutions
👉 💡 The best way to fix that would be to wrap the parent or child component (or both) so we have only one root element.
But if you absolutely need both to have multi-root nodes, you can:
Use a non-scoped style
<style>
.my-p { color: red; }
</style>
Use CSS Modules
<template>
<h1>My awesome component</h1>
<MyChildComponent :class="$style.trick" />
</template>
<style module>
.trick {
color: red;
}
</style>
Since we are specifying a class here, then the multi-root child component has to explicitly specify the attribute fallthrough behavior.
If you want my opinion, unless you absolutely need a multi-root node component, go with a single root node and don't deal with this design limitation at all.
I think it is related to where you apply the main class.
Try to put a wrapper around like that:
<template>
<div class="accept-button">
<OwnButton
#ownButtonClicked="emit('accept')"
>
<slot>
ACCEPT
</slot>
</OwnButton>
</div>
</template>
<script setup>
import OwnButton from './Comp.vue';
const emit = defineEmits(['accept']);
</script>
<style scoped>
.accept-button :deep(.own-button)
{
background-color : yellow !important;
outline-color : green !important;
}
.accept-button :deep(.own-button:hover)
{
background-color: green !important;
}
</style>
Here is the example above.

Vue Video Background does not start

I'm starting as a frontend developer with VueJs and I'm having a problem that I can't understand... So I hope if some could help me... I'm currently using avidofood
/
vue-responsive-video-background-player
Because I need some kind of video playlist in the background. I implemented such as official documentation said (at least I think so..) but nothing happened, I mean, I have no errors, but the video does not start. This is my code:
In App.vue
<template>
<div id="App">
<video-background
src="https://www.youtube.com/watch?v=PpAxlizirDI"
style="max-height: 80%; height: 100vh"
>
<h1 style="color: white">Test App!</h1>
</video-background>
<div class="container mt-5">
<div class="row">
<div class="col-xl-12 col-lg-10">
<div class="form-group barcode">
<div class="row">
<div class="col">
<input
id="barcode"
type="text"
name="barcode"
class="form-control pl-3 shadow-none bg-transparent border-0"
placeholder="Acerca el producto al lector..."
autofocus
onfocus="this.value=''"
/>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
//Oculto el mouse
//document.body.style.cursor = "none";
export default {
name: "App",
};
</script>
<style>
#myVideo {
position: fixed;
right: 0;
bottom: 0;
min-width: 100%;
min-height: 100%;
}
body {
background: #eee;
}
.text-color {
color: rgb(212, 14, 14) !important;
}
.barcode {
border: 1px solid;
border-color: #cccccc;
border-radius: 45px;
}
.container {
position: absolute;
bottom: 0px;
left: 10%;
margin: 0 auto;
}
</style>
In main.js
import Vue from 'vue'
import BootstrapVue from 'bootstrap-vue/dist/bootstrap-vue.esm';
import App from './App.vue'
import 'bootstrap/dist/css/bootstrap.css';
import 'bootstrap-vue/dist/bootstrap-vue.css';
import VideoBackground from "vue-responsive-video-background-player";
Vue.component("video-background", VideoBackground);
Vue.use(BootstrapVue);
Vue.config.productionTip = false
new Vue({
render: h => h(App),
}).$mount('#App')
In Vue dev Tool:
In Chrome Console:
This is de Project Structure:
I hope someone could help me because I have no little idea what's going on.
Thanks in advance!
As source, you have a YouTube page, this is not a video.
<video-background
src="https://www.youtube.com/watch?v=PpAxlizirDI"
style="max-height: 80%; height: 100vh"
>
This is your path to your video. You can just use this value for
showing your video in every resolution.
The source of the video has to be in a format the browser can play (mp4 is a good format to use).
For example:
<video-background
src="/files/my-video.mp4"
style="max-height: 80%; height: 100vh">
Update based on your comment:
Because vue gets compiled it does not know what assets/demo.mp4 is,. You should import it first via an loader.
<script>
import video from 'assets/demo.mp4' // Or '#/assets/demo.mp4' depending on what loader you use.
export default {
name: "App",
};
</script>
And now you could use:
<video-background
:src=video
style="max-height: 80%; height: 100vh">

How do I convert my vue into a component.vue

I have a fiddle that changes the contrast and brightness of an image.
When I try and add it as a .vue component onto my site the slider no longer effects the image. I know the issue is around my filter function. I just can't understand why my :style attribute isn't applying the change to me element.
What am I doing wrong/not getting?
fiddle that works - https://jsfiddle.net/BBMAN/fxtrtqpj/14/
code for my .vue component that does not work.
<template>
<div class="brightness-slider container">
<h1>{{ msg }}</h1>
<h2>Built using Vue.js</h2>
<div class="row">
<div class="col-md-10">
<img ref="img" class="img img-responsive" :style="filters" />
</div>
<div class="col-md-2">
<strong>Contrast ({{contrast}})</strong>
<input class="slider vertical" type="range" orient="vertical" v-model="contrast" max="1" min="0" step="0.01" />
</div>
</div>
<div class="row">
<div class="col-md-12">
<h4>
<strong>Brightness ({{brightness}})</strong>
</h4>
<input class="slider horizontal" type="range" v-model="brightness" max="3" min="0" step="0.01" />
</div>
</div>
</div>
</template>
<script>
export default {
name: 'ImageBrightnessSlider',
data() {
return {
//sending data to view.
msg: 'Audience Republic Brightness Modifier',
brightness: 1,
contrast: 1
}
},computed: {
filters() {
const toDash = (str) => str.replace( /([a-z])([A-Z])/g, '$1-$2' ).toLowerCase()
debugger;
return { filter: Object.entries(this._data).filter(item => typeof(item[1]) !== 'object').map(item => `${toDash(item[0])}(${item[1]})`).join(' ') }
}
},
mounted() {
this.$refs.img.src = require('../assets/pleasure-garden-1200-by-768.jpg')
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
h1,
h2 {
font-weight: normal;
}
ul {
list-style-type: none;
padding: 0;
}
li {
display: inline-block;
margin: 0 10px;
}
a {
color: #42b983;
}
input[type=range][orient=vertical]{
writing-mode: bt-lr; /* IE */
-webkit-appearance: slider-vertical; /* WebKit */
width: 8px;
height: 100%;
padding: 0 5px;
}
.slider {
-webkit-appearance: none;
width: 100%;
height: 3px;
border-radius: 5px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 25px;
height: 25px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 25px;
height: 25px;
border-radius: 50%;
background: #4CAF50;
cursor: pointer;
}
</style>
"fiddle that works" = incorrect
you are trying to shove HTML CSS JavaScript into Javascript!!!
interpreter Javascript does not understand HTML CSS!
you code must be something like this
index.html
<div id="App">
<!-- HTML code -->
</div>
<script>
// js code
</script>
<style scoped>
/* css code */
</style>
you have many mistakes, please see official documentation
also, you can see my vue examples
UPDATED:
SFC example

Transform Based Animation using Vue Transition Wrapper

I'm trying to port some animation implemented mostly through css and little javascript to a Vue component. The animation is simple - user clicks a button and a little panel opens from the bottom of his browser and slides upwards.
I have a working Vue component implemented using the same css and no javascript.
Now, I'm aware of the transition wrapper that Vue provides. But I'm unable to figure out how to get similar functionality using the transition wrapper (if at all).
Can someone help me out here?
// register modal component
Vue.component('modal', {
template: '#modal-template',
props: ['show']
})
// start app
new Vue({
el: '#app',
data: {
showModal: false
}
})
.drawer-wrapper {
position: fixed;
top: 100%;
left: 0;
right: 0;
height: 50%;
z-index: 9998;
transition: transform .3s ease-out;
}
.drawer-wrapper.open {
transform: translateY(-100%);
}
.drawer-container {
height: 100%;
width: 100%;
background-color: white;
}
.drawer-header h3 {
margin-top: 0;
color: #42b983;
}
.drawer-body {
margin: 20px 0;
}
.drawer-default-button {
float: right;
}
<script src="https://unpkg.com/vue#latest/dist/vue.js"></script>
<!-- template for the modal component -->
<script type="text/x-template" id="modal-template">
<div class="drawer-wrapper" :class="{ open: show }">
<div class="drawer-container">
<div class="drawer-header">
<slot name="header">
default header
</slot>
</div>
<div class="drawer-body">
<slot name="body">
default body
</slot>
</div>
<div class="drawer-footer">
<slot name="footer">
default footer
<button class="drawer-default-button" #click="$emit('close')">
OK
</button>
</slot>
</div>
</div>
</div>
</script>
<!-- app -->
<div id="app">
<button id="show-modal" #click="showModal = true">Show Modal</button>
<!-- use the modal component, pass in the prop -->
<modal #close="showModal = false" :show="showModal">
<!--
you can use custom content here to overwrite
default content
-->
<h3 slot="header">custom header</h3>
</modal>
</div>
I got this to work using a transition wrapper, but the code isn't as elegant as I'd expected. Particularly the use of the following attributes in the drawer-wrapper class:
top: 100%;
transform: translateY(-100%);
Fiddle is here
If anyone can simplify the code further, I'd really appreciate it.