How to scroll div by a specific pixel in vue? - javascript

I am trying to scroll up a div 10px by following method in Vue2 app:
this.$refs.hello.scrollTo({ top: 10, left: 0, behavior: "smooth"});
However, instead of scroll to top 10px, it scrolls to the top. How can I scroll to a specific height.
codesandbox example : https://codesandbox.io/s/youthful-blackwell-wl5x6?file=/src/components/HelloWorld.vue:531-630

Good apprch will be, to scroll to the current scrollHeight of the reference element after updating the list.
Demo
new Vue({
el: '#app',
name: "HelloWorld",
props: {
msg: String,
},
data() {
return {
list: [],
};
},
mounted() {
let i = 0;
for (i = 0; i < 19; i++) {
this.list.push(i);
}
},
methods: {
addItem() {
this.list.push(0);
setTimeout(() => {
const targetTop = this.$refs.hello.scrollHeight;
this.$refs.hello.scrollTo({
top: targetTop,
left: 0,
behavior: "smooth",
});
})
},
}
})
.hello {
overflow-y: auto;
height: 200px;
border: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.4/vue.js"></script>
<div id="app">
<div class="hello" ref="hello">
<h1>{{ msg }}</h1>
<div v-for="item in list" :key="item">
<div style="margin-bottom: 20px">{{ item }}</div>
</div>
<button #click="addItem">add</button>
</div>
</div>

Related

Using onclick event, how to match name with multiple status by drawing lines in Vuejs?

new Vue({
el: "#app",
data: {
getQuestionAnswers: [
{
name: 'foo',
checked: false,
status: 'ok'
},
{
name: 'bar',
checked: false,
status: 'notok'
},
{
name: 'baz',
checked: false,
status: 'medium'
},
{
name: 'oo',
checked: false,
status: 'medium'
}
]
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
width:100%
}
.red {
color: red;
}
.bcom {
width: 100%;
display: flex;
}
.container1 {
width: 50px;
}
.container2 {
width: calc(100% - 105px);
padding: 8px 0;
height: 30px;
box-sizing: border-box;
}
.h-line {
height: 1px;
margin-bottom: 18px;
width: 100%;
background-color: black;
}
.container3{
margin-left: 5px;
width: 50px;
}
.point:hover {
width: 200px;
}
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<div id="app">
<div class="bcom"
v-for="(group, index) in getQuestionAnswers"
:key="index + group.name"
:group="group"
>
<div>
<input type="checkbox" v-model="group.checked"/>
{{ group.name }}
</div>
<div class="container2">
<div class="h-line" v-if="group.checked"></div>
</div>
<div>
<input type="checkbox"/>
{{ group.status }}
</div>
</div>
</div>
Onclick of checkbox, how to add multiple lines from one point in Vuejs?
As seen in the image, On click of the checkbox, Based on the status, I need to match from one point to three multiple status. like "ok, notok, medium"
i have taken v-model in the checkbox,to check and perfome two way data binding But not sure....what to do further. Do I need to take computed property and write condition to check and draw three multiple lines???
there are som positioning issues here, but this sample should be enough for you to get it working:
template
<div id="demo" :ref="'plane'">
<canvas :ref="'canvas'"></canvas>
<div
class="bcom"
v-for="(group, index) in getQuestionAnswers"
:key="index + group.name"
:group="group"
>
<div>
<input
type="checkbox"
v-on:click="() => onToggleCheckbox(group)"
v-model="group.checked"
:ref="'checkbox_' + group.name"
/>
<span>{{ group.name }}</span>
</div>
<div>
<span>{{ group.status }}</span>
<input type="checkbox" :ref="'status_' + group.name" />
</div>
</div>
</div>
script:
export default {
name: 'App',
data: () => ({
ctx: undefined,
draw(begin, end, stroke = 'black', width = 1) {
if (!this.ctx) {
const canvas = this.$refs['canvas'];
if (!canvas?.getContext) return;
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
this.ctx = canvas.getContext('2d');
}
if (stroke) {
this.ctx.strokeStyle = stroke;
}
if (width) {
this.ctx.lineWidth = width;
}
this.ctx.beginPath();
this.ctx.moveTo(...begin);
this.ctx.lineTo(...end);
this.ctx.stroke();
},
onToggleCheckbox(group) {
const planeEl = this.$refs['plane'];
const planeRect = planeEl.getBoundingClientRect();
const fromEl = this.$refs['checkbox_' + group.name];
const fromRect = fromEl.getBoundingClientRect();
const from = {
x: fromRect.right - planeRect.left,
y: fromRect.top + fromRect.height / 2 - planeRect.top,
};
const toEl = this.$refs['status_' + group.name];
const toRect = toEl.getBoundingClientRect();
const to = {
x: toRect.left - planeRect.left,
y: toRect.top + toRect.height / 2 - planeRect.top,
};
console.log(planeRect, from, to);
this.draw(
Object.values(from),
Object.values(to),
group.checked ? 'white' : 'black',
group.checked ? 3 : 2
);
},
getQuestionAnswers: [
{
name: 'foo',
checked: false,
status: 'ok',
},
{
name: 'bar',
checked: false,
status: 'notok',
},
{
name: 'baz',
checked: false,
status: 'medium',
},
{
name: 'oo',
checked: false,
status: 'medium',
},
],
}),
};
style
body {
background: #20262e;
padding: 20px;
font-family: Helvetica;
}
#demo {
position: relative;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
canvas {
position: absolute;
background: red;
width: 100%;
height: 100%;
left: 0;
top: 0;
background: #fff;
z-index: -1;
}
.bcom {
width: 100%;
display: flex;
justify-content: space-between;
z-index: 2;
}
this only draws one line but you could easily add the others. I figured you might change your data schema to something like:
getQuestions() {
{
name: string,
checked: boolean,
statuses: [string...],
},
getStatuses() {
{
name: string
}
but not knowing about your requirements here, I decided to post the above before making further changes. (here is the sort of refactor I was referring to: https://stackblitz.com/edit/vue-yuvsxa )
addressing first comment:
in app.vue only there is one data called[((questions))], inside question we are looping and setting the status.
this is easy to address with a bit of preprocessing:
questionsAndStatusesMixed: // such as [{...question, ...statuses}],
questions: [],
statuses: [],
mounted() {
const statusesSet = new Set()
this.questionsAndStatusesMixed.forEach(item => {
const question = {
name: item.name,
checked: item.checked,
answer: item.status // is this the answer or .. these never made sense to me,
statuses: this.statuses // assuming each question should admit all statuses/that is, draw a line to each
}
const status = {
name: item.name
}
this.questions.push(question)
statusesSet.add(status)
})
Array.from(statusesSet).forEach(item => this.statuses.push(item))
}

How check old data in vue component after rendering?

I'm trying to create a simple marketcap checker for crytpo (like coinmarketcap) using coingecko api.
I can fetch the data and render it, no problem with that. And I fetch the data 2 times per minutes.
But now, I would like to check if the new price is higher or lower than the last price.
I do a v-for loop and I pass some data in my "tokenComponent" for rendering the data like this :
<template>
<div id="app">
<div class="container mx-auto">
<div class="pt-6">
<h1 class="text-2xl font-bold">Crypto Monkey Cap</h1>
<div v-for="token in listTokens" :key="token.id">
<div class="py-6">
<token-component
:name="token.name"
:price="token.current_price"
:mcap="token.market_cap"
></token-component>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import TokenComponent from "./components/tokenComponent.vue";
export default {
name: "App",
components: {
TokenComponent,
},
data() {
return {
listTokens: [],
lastPrice: 0
};
},
mounted() {
this.getTokens();
setInterval(() => {
this.getTokens()
}, 30000);
},
methods: {
getTokens() {
fetch("https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd")
.then((response) => response.json())
.then((data) => {
this.listTokens = data;
});
}
}
};
</script>
and the tokenComponent :
<template>
<div class="py-4 border-2 rounded-lg">
<div class="flex justify-around">
<h2>{{ name }}</h2>
<h2>{{ price }} $</h2>
<h2>{{ mcap }} $</h2>
</div>
</div>
</template>
<script>
export default {
props: {
name: { required: true },
price: { required: true },
mcap: { required: true }
}
};
</script>
I just would like put a conditionnal class in price data if the last price is higher or lower than the new one...
(I'm new in Vuejs... ;) )
You should store previous prices to calculate if the last price is higher or lower than the new one. Use Array for that.
Added small example using setInterval instead of fetching new prices to display indicators
new Vue({
el: "#app",
data: () => ({
prices: [1]
}),
methods: {
stonks(index) {
if (index > 0) {
return (this.prices[index] - this.prices[index-1]) > 0
? 'green' : 'red'
}
}
},
mounted() {
setInterval(() => {
this.prices.push(Math.floor(Math.random() * 10) + 1)
}, 2000)
}
})
.prices {
display: flex;
flex-direction: row;
padding: 10px;
}
.price {
border:1px solid #bbb;
border-radius: 5px;
padding: 10px;
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
margin-right: 5px;
position: relative;
}
.stonks {
position: absolute;
background: grey;
border-radius: 50%;
width: 16px;
height: 16px;
top: 0;
right: 0;
margin-top:-8px;
margin-right:-8px
}
.stonks.red { background: red; }
.stonks.green { background: green; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="prices">
<div
v-for="(price, index) in prices"
:key="index"
class="price"
>
{{ price }}
<div class="stonks" :class="stonks(index)" />
</div>
</div>
</div>

Icons instead of text as tabs in Vue.js

I wanted to make the lower menu similar to VK, where you click on the icons and the content changes. I implemented different content, but I can't make each tab have its own icon.
I provide a screenshot and code. Do not pay attention to the style, I first need to understand the logic Using Vue CLI.
<template>
<div id="app">
<font-awesome-icon
icon="user-secret"
v-for="tab in tabs"
:key='tab'
#click="currentTab = tab"
:class="['tab-button', {active: currentTab === tab}]"
>
{{ tab }}
</font-awesome-icon>
<component v-bind:is="currentTabComponent"></component>
</div>
</template>
<script>
import Posts from './Posts.vue'
import List from './List.vue'
export default {
data () {
return {
currentTab: 'Posts',
tabs: ['Posts', 'List'],
icon: 'bell'
}
},
components: {
tabPosts: Posts,
tabList: List
},
computed: {
currentTabComponent() {
return 'tab-' + this.currentTab.toLowerCase()
}
}
}
</script>
<style scoped>
body {
overflow: auto;
padding: 0;
margin: 0;
}
#app {
position: relative;
width: 320px;
height: 400px;
border: solid 1px black;
}
.tab-button.active {
position: relative;
color: red;
height: 50px;
width: 50px;
}
.tab-button {
position: relative;
float: right;
bottom: 10px;
height: 50px;
width: 50px;
}
</style>
Try this:
<div
v-for="tab in tabs"
:key='tab'
#click="currentTab = tab"
:class="['tab-button', {active: currentTab === tab}]"
>
<font-awesome-icon >
icon="user-secret"
</font-awesome-icon >
</div>
also this #click="currentTab = tab" needs to be refactored
I'll assume that this issue has already been solved, but, in any case, here is my contribuition using fontAwesome so you can understand the logic:
Change the definition of your data tabs and currentTab to something like this:
currentTab: {title:'Post section',icon:'bell',page:'Post'},
tabs:{
title:'Post section',
icon:'bell',
page:'Post'
},
{
title:'List of posts',
icon:'list',
page:'List'
}
Convert your font-awesome-icon tag to:
<button v-for="tab in tabs" :key="tab"
:class="['tab-button', { active: currentTab === tab }]" #click="currentTab = tab"
title="tab.title">
<i class="fa" :class="'fa-'+tab.icon"></i>
</button>
Finally change you component tag to:
<component :is="currentTab.page" class="tab"></component>
And them you can ignore the whole compuded: {} section of your export default

Index not defined while deleting element from array

I am trying to remove element from array if animation ends, but i get error: index is not defined.
How to correctly find specific index and remove it if animation ends?
They are drop() and remove() methods. drop() methods works well(i think) and elements are correctly added to the DOM.
Single file component looks like this:
<template>
<div class="card" :class="classObject">
<div class="card-image">
<figure class="image" #click="randomImage">
<img src="../../img/one.png" alt="Placeholder image" v-if="selected === 0">
<img src="../../img/two.jpg" alt="Placeholder image" v-else-if="selected === 1">
<img src="../../img/three.jpg" alt="Placeholder image" v-else>
</figure>
</div>
<div class="card-content has-text-centered">
<div class="content">
<div class="title is-size-1 has-text-weight-bold">
<span v-show="score >= 10">🎉</span>
{{score}}
<span v-show="score >= 10">🎉</span>
</div>
<div v-if="score >= 5" class="has-text-grey">
╮ (. ❛ ᴗ ❛.) ╭
</div>
<div v-else-if="score < 5 && score > 0" class="has-text-grey">
༼ つ ◕_◕ ༽つ
</div>
<div v-else class="has-text-grey">
(・_・ヾ
</div>
</div>
</div>
<footer class="card-footer">
<a class="card-footer-item" #click="score++">more 👍</a>
<a class="card-footer-item" #click="score--">less 👎</a>
<a class="card-footer-item" #click="drop" :disabled="score < 1">butt 💩</a>
</footer>
<transition-group name="drop" v-on:after-enter="remove(index)">
<img src="../../img/image.png" class="image" alt="an image" v-for="(item, index) in items" :key="index">
</transition-group>
</div>
</template>
<script>
export default {
data() {
return {
score: 23,
selected: 0,
images: [
'./img/one.png',
'./img/two.jpg',
'./img/three.jpg'
],
items: []
}
},
methods: {
debug(data) {
console.log(data);
},
randomImage() {
this.selected = Math.floor((Math.random() * 3))
},
drop() {
this.items.push(this.item);
},
remove(item) {
this.items.splice(item, 1);
}
},
computed: {
image() {
return this.selected;
},
classObject() {
return {
hard: this.score >= 42,
sixnine: this.score == 69
}
}
}
}
</script>
<style>
.image {
position: absolute;
top: calc(0vh - 500px);
left: 0;
right: 0;
/* pointer-events: none; */
/* top: 50%;
left: 50%;
transform: translate(-50%); */
}
.drop-enter-active {
transition: transform 3s;
}
.drop-enter {
transform: translateY(0vh);
}
.drop-enter-to {
transform: translateY(calc(100vh + 500px));
}
</style>
An Instance of finding the specific index and remove it from the array.
In below snippet, find the index which has { id: 2 } and removes it from an array.
const array = [{ id: 1 }, { id: 2 },
{ id: 3 }];
const index = array.findIndex((f) =>
{ return f.id && f.id === 2; });
console.log(index);
if(index > -1) {
// remove entry from found index
array.splice(index, 1);
console.log(array);
}
The issue comes when you are trying to call a function when a reference is needed. Instead of v-on:after-enter="remove(index)", try this v-on:after-enter="remove". So, when the v-on:after-enter is triggered its going to call the remove(item) using the reference you've already given to it. If you are giving a reference to a function you only use the name of that function.
You can find index of element in array with findIndex method:
remove(item) {
const index = this.items.findIndex(arrayItem => arrayItem === item);
this.items.splice(index, 1);
}
I modified the code to make it more obvious what I want to do
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.5/css/bulma.min.css" integrity="sha256-vK3UTo/8wHbaUn+dTQD0X6dzidqc5l7gczvH+Bnowwk=" crossorigin="anonymous" />
<title>life is vuetiful</title>
</head>
<body class="has-background-primary">
<style>
html {
background-color: transparent;
}
body {
width: 42%;
margin: 2em auto;
}
a[disabled] {
color: grey;
cursor: default;
background-color: lightgray;
}
.hard {
border: 10px solid purple;
}
.sixnine {
background-color: pink;
border: 20px solid hotpink;
outline: 15px solid pink;
}
.image {
position: absolute;
top: calc(0vh - 500px);
left: 0;
right: 0;
/* pointer-events: none; */
/* top: 50%;
left: 50%;
transform: translate(-50%); */
}
.drop-enter-active {
transition: transform 3s;
}
.drop-enter {
transform: translateY(0vh);
}
.drop-enter-to {
transform: translateY(calc(100vh + 500px));
}
</style>
<div id="app">
<test></test>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('test', {
template:
`
<div class="card">
<footer class="card-footer">
<a class="card-footer-item" #click="drop">run</a>
</footer>
<transition-group name="drop" v-on:after-enter="remove(index)">
<img src="https://picsum.photos/id/237/200/300" class="image" v-for=" (item, index) in items" :key="index" alt="an image">
</transition-group>
</div>
`
,
data() {
return {
items: []
}
},
methods: {
drop() {
this.items.push(this.item);
},
remove (index) {
this.$delete(this.items, index);
}
}
})
</script>
<script>
const vue = new Vue({
el: '#app'
})
</script>
</body>
</html>

Vue - splicing a positioned element from an array will apply its offset to next index

I'm quite new to Vue and have been working on a small project that uses a lot of positioned objects on the screen. I've found that slicing an index from my model will cause its positioning to be applied to the next index, resetting its own.
The example below is related (demonstrates similar unexpected behaviour), but not entirely my issue - being the offset transferring to the next index.
new Vue({
el: '#app',
data: {
items: [0, 1, 2, 3]
},
methods: {
take: function(i) {
var item = $(i.target).closest('.sort').index();
this.items.splice(item, 1);
}
}
});
button {
position: relative;
bottom: 0;
margin-top: 25px;
}
.sort {
padding: 15px;
margin: 5px;
display: inline-block;
background: grey;
position: relative;
}
.sort:nth-child(1) {
top: 54px;
left: 140px;
}
.sort:nth-child(2) {
top: 155px;
left: 230px;
}
.sort:nth-child(3) {
top: 226px;
left: 32px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="app">
<div class="sort" v-for="item in items">
{{ item }}
<span class="remove" v-on:click="take">x</span>
</div>
</div>
The expected behaviour is for the items to stay positioned as they were until they're deleted themselves. My current solution is to take a copy of the next index's styles and apply them again. Any help would be appreciated, thanks!
When you splice out an item from items it is removed from the DOM-binded array. Because you are using nth-child(1) to nth-child(3) the positions are shifted as a deleted element shifts the remaining up.
So I have some suggestions for you:
Give that the positions of each element is specific, so why not put it in the data like {item: 0, class: 'zero'} for example - and there is a change in markup:
<div v-bind:class="['sort', item.class]" v-for="item in items">
{{ item.item }} <span class="remove" v-on:click="take(item)">x</span>
</div>
Instead of calculating index like you have done, its better to pass item as an argument as index can vary if you apply ordering/filtering.
Still the positions will not be correct- its better to keep app as relative and apply absolute for all of the sort classes.
See demo below:
new Vue({
el: '#app',
data: {
items: [{
item: 0,
class: 'zero'
}, {
item: 1,
class: 'one'
}, {
item: 2,
class: 'two'
}, {
item: 3,
class: 'three'
}]
},
methods: {
take: function(item) {
this.items.splice(this.items.indexOf(item), 1);
}
}
});
button {
position: relative;
bottom: 0;
margin-top: 25px;
}
#app {
position: relative;
}
.sort {
padding: 15px;
margin: 5px;
display: inline-block;
background: grey;
position: absolute;
}
.remove {
cursor: pointer;
}
.sort.zero {
top: 54px;
left: 140px;
}
.sort.one {
top: 155px;
left: 230px;
}
.sort.two {
top: 226px;
left: 32px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js"></script>
<div id="app">
<div v-bind:class="['sort', item.class]" v-for="item in items">
{{ item.item }} <span class="remove" v-on:click="take(item)">x</span>
</div>
</div>

Categories

Resources