How to change vue.js directive depending on viewport? - javascript

I have an element with the v-rellax directive, which is used to enable prallax scrolling in this div:
<div id="image" v-rellax="{ speed: -5 }"></div>
Now I need to change the speed property to -3 to adapt it to a different screen width. Is there a way to change speed:
1. in a `media query`
2. in `vue.js`
3. in `javascript`
Is there a simple way to do that with vue?
Edit:
I've tried implementing offered solution but the parallax scroll stops working. Have I done it wrong?
<script>
export default {
name: "home",
data: { rellax_speed: -5 },
created() {
window.addEventListener('resize', (event) => {
if (event.target.innerWidth >= 576) {
this.rellax_speed = -3;
return;
}
this.rellax_speed = -5;
})
}
}
</script>
template:
<div id="image" v-rellax="{ speed: rellax_speed }"></div>
Edit2:
<script>
export default {
name: "home",
data() {
return {
r_speed: -5
}
},
methods: {
onresize(event) {
if (event.target.innerWidth >= 576) {
this.r_speed = -3;
return;
}
this.r_speed = -5
}
},
created() {
window.addEventListener("resize", this.onresize)
},
beforeDestroy() {
window.removeEventListener("resize", this.onresize, true)
}
}
</script>
template:
<div id="image" v-rellax="{ speed: r_speed }"></div>

Okay I believe to understand you.
EDIT:
Thank you tony19 :)
Create vrellaxSpeed in data. Initial value: -5, use it in your directive.
data() {
return {
vrellaxSpeed: -5
}
},
methods:{
onResize(event) {
if (event.target.innerWidth > 1280) {
this.vrellaxSpeed = -3;
return;
}
this.vrellaxSpeed = -5;
}
},
created() {
window.addEventListener('resize', this.onResize)
},
beforeDestroy() {
window.removeEventListener('resize', this.onResize, true);
}
Create a computed in your vue component.
computed: {
getVrellaxSpeed() {
return window.innerWidth > 1280 ? -3 : -5;
}
}
Then, use it in your directive prop.
<div id="image" v-rellax="{ speed: getVrellaxSpeed }"></div>
I did not test it, please try this and give me feedback to help you :)

Related

Use a variable defined in a method inside the template

it's the first time I use Vue (v2 not v3) and I'm stucked trying to use a variable (defined inside a methods) inside the template.
My semplified code:
<template>
<div class="container" #mouseover="isHovered = true" #mouseleave="isHovered = false">
<div class="c-container">
<div ref="topCContainerRef" class="top-c-container">
<div
:class="['top-c', ...]"
:style="{ height: `${isHovered ? 0 : this.scaledHeight}` }" // <-- HERE I need `scaledHeight`
>
</div>
</div>
</div>
</div>
</template>
<script>
import { scaleLinear } from 'd3-scale'
export default {
name: 'MyComponent',
components: { },
props: {
...,
datum: {
type: Number,
required: true,
},
...
},
data: function () {
return {
isHovered: false,
scaledHeight: {},
}
},
mounted() {
this.matchHeight()
},
methods: {
matchHeight() {
const topCContainerHeight = this.$refs.topCContainerRef.clientHeight
const heightScale = scaleLinear([0, 100], [20, topCContainerHeight])
const scaledHeight = heightScale(this.datum)
this.scaledHeight = scaledHeight // I want to use this value inside the template
},
},
}
</script>
How can I get the value of scaledHeight inside the template section?
If I didn't use this, I get no error but the height value is always 0, like scaledHeight is ignored..
I read the documentation but it doesn't help me
I encountered and solved this problem today.
You can change your styles like below.
<div
:class="['top-c', ...]"
:style="{ height: isHovered ? 0 : scaledHeight }"
>
It works fine for me, and hope it will help you~~
Fixed using computed
computed: {
computedHeight: function () {
return this.isHovered ? 0 : this.matchHeight()
},
},
methods: {
matchHeight() {
const topCContainerHeight = this.$refs.topCContainerRef.clientHeight
const heightScale = scaleLinear([0, 100], [20, topCContainerHeight])
return heightScale(this.datum)
},
},

Responsive canvas vuejs

I'm trying to make my canvas fit its container, in a vue component.
I fire resizeCanvas() in mounted, but the container height and width are 0.
How can I have a canvas that fits its container, so that I can change the container size using css and it will update the canvas ?
(Sorry for my english, i'm not fluent)
<template lang="html">
<div class="container" ref="container">
<canvas ref="canvas" :width="width" :height="height"></canvas>
</div>
</template>
<script>
export default {
name: "monster",
data ()
{
return {
width: 512,
height: 512
}
}
methods: {
resizeCanvas(){
console.log(this.$refs.container.offsetWidth); // logs 0
this.width = this.$refs.container.offsetWidth
this.height = this.$refs.container.offsetWidth
}
},
mounted ()
{
console.log(this.$refs.container.offsetWidth) // logs 0
window.addEventListener("resize", this.resizeCanvas);
this.resizeCanvas()
},
unmounted() {
window.removeEventListener("resize", this.resizeCanvas);
},
}
</script>
<style lang="css" scoped>
.container {
width: 100%; height: 100%
}
</style>
The mounted lifecycle hook in VueJS does not guarantee that the DOM is ready: you will need to wait for this.$nextTick() and then check the dimensions of your container element.
You can do it by moving the logic into the callback of nextTick, i.e.:
mounted () {
this.$nextTick(() => {
console.log(this.$refs.container.offsetWidth);
window.addEventListener("resize", this.resizeCanvas);
this.resizeCanvas();
});
},
If you're familiar with the async/await way of doing things, then you can also do this:
async mounted () {
await this.$nextTick();
console.log(this.$refs.container.offsetWidth);
window.addEventListener("resize", this.resizeCanvas);
this.resizeCanvas();
},
Another possibility is to delegate this "wait for DOM to be ready" responsibility to the resizeCanvas function, so you have full abstraction and separation of concerns:
methods: {
async resizeCanvas(){
await this.$nextTick();
this.width = this.$refs.container.offsetWidth
this.height = this.$refs.container.offsetWidth
}
},
mounted () {
window.addEventListener("resize", this.resizeCanvas);
this.resizeCanvas();
},

How to change style of an element based on window width in Vue

I am trying to change the styling on an HTML element when the window size is smaller than 500px. Currently I have a computed property that defines a background-image for the element. I've tried using an if statement inside the computed property to check whether window.innerWidth < 500, but it seems like I can't do logic inside computed properties. My latest attempt was to create a method that checks the window width and if it is less than 500, it reasigns the computed property with the updated value. However, this does not seem to work.
Any help is appreciated. Thanks!
<template>
<div class="hero" :style="bgImage">
</template
data() {
return {
window: {
width: 0
}
}
},
created() {
window.addEventListener('resize', this.handleResize)
this.handleResize()
},
destroyed() {
window.removeEventListener('resize', this.handleResize)
},
methods: {
handleResize() {
if (window.width < 500) {
this.bgImage = `backgroundImage: linear-gradient(to bottom,rgba(245, 246, 252, 0) 45%,rgb(0, 0, 0) 100%), url(${this.hero.image})`
}
}
},
computed: {
bgImage() {
return {
backgroundImage: `url(${this.hero.image})`
}
}
}
Try to use computed to check your width data changes.
I changed your code this way
export default {
data() {
return {
window: {
width: 0,
},
},
computed: {
bgImage() {
if (this.width < 500) {
return {
backgroundImage: `linear-gradient(to bottom,rgba(245, 246, 252, 0) 45%,rgb(0, 0, 0) 100%), url(${this.hero.image})`
}
} else {
return {
backgroundImage: `url(${this.hero.image})`
}
}
}
}
created: function() {
window.addEventListener('resize', this.handleResize);
},
destroyed: function() {
window.removeEventListener('resize', this.handleResize);
},
methods: {
handleResize() {
this.width = window.innerWidth;
}
},
};

How to get height of the just removed element from Vue.js DOM

I have a list of items in Vue.js from index 0 to 49
I show a subset of these items controlled by startIndex and endIndex
When I increment startIndex to 1, items from 1-49 are shown, 0 is removed from DOM
How to get height of 0 that was just removed
Also how to get height of item that was just added if I edit the endIndex?
HTML
<script type="text/x-template" id="virtual-list">
<div id="root" ref="root">
<div id="viewport" ref="viewport">
<div id="spacer" ref="spacer" :style="spacerStyle">
<div v-for="i in visibleItems" :key="i.index" :id="i.index" class="list-item">
{{i.value}}
</div>
</div>
</div>
</div>
</script>
<div id="app">
<button #click="incrementStart">Start +</button>
<button #click="decrementStart">Start -</button>
<button #click="incrementEnd">End +</button>
<button #click="decrementEnd">End -</button>
<virtual-list></virtual-list>
</div>
CSS
* {
box-sizing: border-box;
}
html,
body,
#app {
height: 100%;
}
#app {
padding: 1.25rem;
}
#root {
height: 50%;
overflow-y: auto;
}
.list-item {
padding: 0.75rem 0;
}
Vue.js
const PAGE_SIZE = 50;
const items = new Array(PAGE_SIZE).fill(null).map((item, index) => {
return {
id: faker.random.uuid(),
index: index,
value: "Item " + index + " " + faker.random.words(index % 25)
};
});
const bus = new Vue({});
Vue.component("virtual-list", {
template: "#virtual-list",
data() {
return {
isMounted: false,
items,
startIndex: 0,
endIndex: PAGE_SIZE,
scrollTop: 0,
translateY: 0,
scrollDirection: 0
};
},
computed: {
visibleItems() {
return this.items.slice(this.startIndex, this.endIndex);
},
/**
Translate the spacer verticaly to keep the scrollbar intact
We only show N items at a time so the scrollbar would get affected if we dont translate
*/
spacerStyle() {
return {
willChange: "auto",
transform: "translateY(" + this.translateY + "px)"
};
}
},
methods: {
handleScroll() {
this.scrollTop = this.$el.scrollTop;
this.startIndex = Math.floor(this.scrollTop / 42);
}
},
watch: {
scrollTop(newValue, oldValue) {
if (newValue > oldValue) {
this.scrollDirection = 1;
} else if (newValue < oldValue) {
this.scrollDirection = -1;
}
},
startIndex(newValue, oldValue) {
// console.log(this.$refs.spacer.children);
}
},
beforeUpdate() {
// console.log('before update', this.$refs.spacer.children);
},
mounted() {
this.isMounted = true;
const children = this.$refs.spacer.children;
for (let i = 0; i < children.length; i++) {
// console.log(children[i].offsetTop - this.$el.offsetTop);
children[i].setAttribute("data-height", children[i].scrollHeight);
}
bus.$on("incrementStart", () => {
this.startIndex++;
});
bus.$on("decrementStart", () => {
this.startIndex--;
});
bus.$on("incrementEnd", () => {
this.endIndex++;
});
bus.$on("decrementEnd", () => {
this.endIndex--;
});
this.$el.addEventListener("scroll", this.handleScroll);
},
destroyed() {
this.$el.removeEventListener("scroll", this.handleScroll);
}
});
new Vue({
el: "#app",
methods: {
incrementStart() {
bus.$emit("incrementStart");
},
decrementStart() {
bus.$emit("decrementStart");
},
incrementEnd() {
bus.$emit("incrementEnd");
},
decrementEnd() {
bus.$emit("decrementEnd");
}
}
});
I think it's better to calculate height of container div of those children divs,
You can get height of the element using
element.getBoundingClientRect().height
To access element you can assign a ref to that div and accessing that div like
this.$refs.containerDiv.getBoundingClientRect().height
Afterwards, you can just compare between older value and new value to get how much it decreased / increased.

Vue - change input width according to content

So I can do this (Pug & CoffeeScript):
input(placeholder="0", v-model.number="order[index]" v-on:change="adjustInput")
...
adjustInput: ->
event.target.style.width = event.target.value.length + 'ch'
... but it only works if I change the input in the browser, by hand. The input does not change its width if the v-model changes.
How can I make it so that the input width changes even if the change is due to Vue reactivity?
Check this one, but you must check the font-size on input and on fake_div
var app = new Vue({
el: '#app',
data() {
return {
order: 1, // your value
fakeDivWidth: 10, // width from start, so input width = 10px
};
},
watch: {
order: { // if value from input changing
handler(val) {
this.inputResize();
},
},
},
methods: {
inputResize() {
setTimeout(() => {
this.fakeDivWidth = this.$el.querySelector('.fake_div').clientWidth;
}, 0);
},
},
})
.fake_div {
position: absolute;
left: -100500vw;
top: -100500vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<input placeholder="0" v-model.number="order" v-bind:style="{width: fakeDivWidth + 'px'}" >
<div class="fake_div">{{ order }}</div> // fake_div needed to see this div width
// этот блок фейковый и нужен только для того, что бы наш input становился такого же размера как этот div
</div>
Try this
input(placeholder="0", v-model.number="order[index]" v-on:change="adjustInput" :style="{width: reactiveWidth}")
// Your secret Vue code
data() {
return function() {
reactiveWidth: 100px; // (some default value)
}
},
methods: {
adjustInput() {
this.reactiveWidth = event.target.value.length + 'ch'
}
},
computed: {
reactiveWidth() {
return this.number + 'ch';
}
}
Since I don't know all parts of the code, you might need to tweak this a bit. With just binding this.number to a order[index] you are not affecting the width of the input in any way. The computed property listens to changes in number
The easiest workaround to me is to replace the input field with a contenteditable span which will wrap around the text:
<script setup>
import {reactive} from 'vue'
const state = reactive({
input: 'My width will adapt to the text'
})
</script>
<template>
<span class="input" #input="e => state.input = e.target.innerText" contenteditable>{{state.input}}</span>
</template>
This works like v-model=state.input

Categories