How to trigger function on viewport visible with Vue viewport plugin - vue.js

I am using an counter to display some numbers, but they load up when the page loads, so it loads unless I do some button to trigger it.
Found this viewport plugin (https://github.com/BKWLD/vue-in-viewport-mixin) but I weren't able to use it. That's what I need to do, trigger a function when I reach some element (entirely), how to achieve it?

You don't necessarily need a package to do this. Add an event listener to listen to the scroll event, and check if the element is in the viewport every time there's a scroll event. Example code below - note that I've added an animation to emphasize the "appear if in viewport" effect.
Codepen here.
new Vue({
el: '#app',
created () {
window.addEventListener('scroll', this.onScroll);
},
destroyed () {
window.removeEventListener('scroll', this.onScroll);
},
data () {
return {
items: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12
],
offsetTop: 0
}
},
watch: {
offsetTop (val) {
this.callbackFunc()
}
},
methods: {
onScroll (e) {
console.log('scrolling')
this.offsetTop = window.pageYOffset || document.documentElement.scrollTop
},
isElementInViewport(el) {
var rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
},
callbackFunc() {
let items = document.querySelectorAll(".card");
for (var i = 0; i < items.length; i++) {
if (this.isElementInViewport(items[i])) {
items[i].classList.add("in-view");
}
}
}
}
})
.card {
height: 100px;
border: 1px solid #000;
visibility: hidden;
opacity: 0
}
.in-view {
visibility: visible;
opacity: 1;
animation: bounce-appear .5s ease forwards;
}
#keyframes bounce-appear {
0% {
transform: translateY(-50%) translateX(-50%) scale(0);
}
90% {
transform: translateY(-50%) translateX(-50%) scale(1.1);
}
100% {
tranform: translateY(-50%) translateX(-50%) scale(1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" onscroll="onScroll">
<div v-for="item in items" class="card">
{{item}}
</div>
</div>
Another option is to use an intersection observer - I haven't explored this yet but this tutorial seems good: alligator.io/vuejs/lazy-image. Note that you will need a polyfill for IE.

Related

Create Konvajs Shapes and Connections creating dynamically based on button click events

I would like to create Rectangle Shapes and Connections using the Vue-Konva/Konvajs within my application. I do not want to create load the Static values rather I would like to create the Shapes when the user clicks on the Add Node button and create Connectors when the user clicks on the Add Connector button and build the connections between Shapes.
I looked into a few things and was able to do it using the mouse events but was unable to convert it to button clicks.
Following is the current code I have: CodeSandbox
Can someone please guide me on how to create shapes and connectors on click of the button events? Any suggestion or guidance is much appreciated.
I am looking something like this:
After trying a few things I was able to get it working. Posting here as it can be useful to someone in the future:
<template>
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<button class="btn btn-primary btn-sm" #click="addEvent()">
Add Event
</button>
<button class="btn btn-success btn-sm" #click="submitNodes()">
Submit
</button>
</div>
</div>
<div class="row root">
<div class="col-sm-12 body">
<v-stage
ref="stage"
class="stage"
:config="stageSize"
#mouseup="handleMouseUp"
#mousemove="handleMouseMove"
#mousedown="handleMouseDown"
>
<v-layer ref="layer">
<v-rect
v-for="(rec, index) in nodeArray"
:key="index"
:config="{
x: Math.min(rec.startPointX, rec.startPointX + rec.width),
y: Math.min(rec.startPointY, rec.startPointY + rec.height),
width: Math.abs(rec.width),
height: Math.abs(rec.height),
fill: 'rgb(0,0,0,0)',
stroke: 'black',
strokeWidth: 3,
}"
/>
</v-layer>
</v-stage>
</div>
</div>
</div>
</template>
<script>
export default {
data () {
return {
stageSize: {
width: null,
height: 900
},
lines: [],
isDrawing: false,
eventFlag: false,
nodeCounter: 0,
nodeArray: []
}
},
mounted () {
if (process.browser && window !== undefined) {
this.stageSize.width = window.innerWidth
// this.stageSize.height = window.innerHeight
}
},
methods: {
handleMouseDown (event) {
if (this.eventFlag) {
this.isDrawing = true
const pos = this.$refs.stage.getNode().getPointerPosition()
const nodeInfo = this.nodeArray[this.nodeArray.length - 1]
nodeInfo.startPointX = pos.x
nodeInfo.startPointY = pos.y
console.log(JSON.stringify(nodeInfo, null, 4))
}
},
handleMouseUp () {
this.isDrawing = false
this.eventFlag = false
},
setNodes (element) {
this.nodeArray = element
},
handleMouseMove (event) {
if (!this.isDrawing) {
return
}
// console.log(event);
const point = this.$refs.stage.getNode().getPointerPosition()
// Handle rectangle part
const curRec = this.nodeArray[this.nodeArray.length - 1]
curRec.width = point.x - curRec.startPointX
curRec.height = point.y - curRec.startPointY
},
// Function to read the Nodes after add all the nodes
submitNodes () {
console.log('ALL NODE INFO')
console.log(JSON.stringify(this.nodeArray, null, 4))
this.handleDragstart()
},
addEvent () {
this.eventFlag = true
this.setNodes([
...this.nodeArray,
{
width: 0,
height: 0,
draggable: true,
name: 'Event ' + this.nodeCounter
}
])
this.nodeCounter++
}
}
}
</script>
<style scoped>
.root {
--bg-color: #fff;
--line-color-1: #D5D8DC;
--line-color-2: #a9a9a9;
}
.body {
height: 100vh;
margin: 0;
}
.stage {
height: 100%;
background-color: var(--bg-color);
background-image: conic-gradient(at calc(100% - 2px) calc(100% - 2px),var(--line-color-1) 270deg, #0000 0),
conic-gradient(at calc(100% - 1px) calc(100% - 1px),var(--line-color-2) 270deg, #0000 0);
background-size: 100px 100px, 20px 20px;
}
</style>

How to chain transitions/animation in Vue?

Target
On click "Open menu" button:
Dim overlay appearing with fade-in animation
Once dim overlay animation done, from the top, dim overlay is appearing with the sliding animation from the top to bottom:
Solution attempt and problem
<template lang="pug">
transition(name="fade")
.DrawerMenu-DimUnderlay(v-if="displayFlag")
.DrawerMenu-Body Drawer menu
</template>
Before slide down the .DrawerMenu-Body, .DrawerMenu-DimUnderlay must be mounted and rendered.
I don't know how to implement it.
🌎 Fiddle
You can achieve that by using CSS Animations and Vue Transition.
First, separate your overlay and content into different transitions:
<template lang="pug">
div
transition(name="overlay")
.DrawerMenu-Overlay(v-if="displayFlag")
transition(name="content")
.DrawerMenu-Body(v-if="displayFlag") Drawer menu
</template>
Then define your animations:
.DrawerMenu {
&-Overlay {
...
display: none;
}
...
}
.overlay-enter-active {
display: block;
animation: fade-in-and-slide-down 2s;
}
.content-enter-active {
animation: wait-and-fade-in 3s;
}
.content-leave-active {
animation: fade-out 1s;
}
#keyframes fade-in-and-slide-down {
0% {
opacity: 0;
}
50% {
opacity: 1;
transform: translateY(0);
}
100% {
transform: translateY(100%);
}
}
#keyframes wait-and-fade-in {
0% {
opacity: 0;
}
66% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#keyframes fade-out {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
Example in CodeSandbox.
Another solution is using JavaScript animation library (such as animejs) combine with Vue Transition Hooks. I would prefer this solution for a complex animation.
<template lang="pug">
transition(#enter='enter' #leave='leave')
div(v-if='displayFlag')
.DrawerMenu-Overlay(ref='overlay')
.DrawerMenu-Body(ref='content' #click="displayFlag = false") Drawer menu
</template>
import anime from "animejs";
...
methods: {
enter(el, done) {
anime
.timeline({
easing: "linear",
duration: 1000,
complete: done
})
.add({
targets: this.$refs.overlay,
opacity: [0, 1]
})
.add({
targets: this.$refs.overlay,
translateY: "100%"
})
.add({
targets: this.$refs.content,
opacity: [0, 1]
});
},
leave(el, done) {
anime({
targets: el,
duration: 2000,
opacity: 0,
complete: done
});
},
...
}
...
You can also use without transition component but you have to handle v-if variable by yourself.
Example in CodeSandbox.
Not sure if there are 2 questions here, but for your last question, I would say that is because that ref component does not have a property display.
It does however, have a function display()
Therefore, change your button click to this:
<button #click="$refs.drawerMenu.display()">Open menu</button>

Computed styles not applied during leave transition

When an element has a computed style, the style changes are not applied if the element is going through a leave transition:
new Vue({
el: "#app",
data: {
selected: 1,
items: [{
color: 'red'
},
{
color: 'blue'
},
{
color: 'green'
},
],
tweened: {
height: 50,
},
},
computed: {
divStyles() {
return {
height: this.tweened.height + 'px',
background: this.displayed.color,
'margin-left': this.selected * 100 + 'px',
width: '100px',
}
},
displayed() {
return this.items[this.selected - 1]
}
},
watch: {
selected(newVal) {
function animate() {
if (TWEEN.update()) {
requestAnimationFrame(animate)
}
}
new TWEEN.Tween(this.tweened)
.to({
height: newVal * 50
}, 2000)
.easing(TWEEN.Easing.Quadratic.InOut)
.start()
animate()
}
},
methods: {
toggle: function(todo) {
todo.done = !todo.done
}
}
})
.colored-div {
opacity: 1;
position: absolute;
}
.switcher-leave-to,
.switcher-enter {
opacity: 0;
}
.switcher-enter-to,
.switcher-leave {
opacity: 1;
}
.switcher-leave-active,
.switcher-enter-active {
transition: opacity 5s linear;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.21/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/16.3.5/Tween.min.js"></script>
<div id="app">
<button #click="selected--" :disabled="selected <= 1">
Previous
</button>
<button #click="selected++" :disabled="selected >= 3">
Next
</button>
<span>Selected: {{selected}}</span>
<transition name="switcher">
<div v-for="(item, index) in items" v-if="index + 1 === selected" :key="index" :style="divStyles" class="colored-div" />
</transition>
</div>
https://jsfiddle.net/64syzru5/12/
I would expect the leaving element to continue resizing as it fades out, but it doesn't. What can be done to have the computed styles applied to the leaving element during the leave-active transition?
Since you're using CSS for the transitions, Javascript doesn't execute at each intermediate step. That's a good thing for performance, but it means that the computed properties aren't recomputed. As best as I can tell, though, you're just trying to animate the height. That's easily accomplished in pure CSS. Use a before-leave hook to set it to an initial value via an inline style or CSS variable, and then remove that property in the after-leave hook.
More to the point, though, it looks like your application might be more suitable for a transition-group instead of a simple transition.

Window.resize or document.resize which works & which doesn't? VueJS

I am using Vuetable and its awesome.
I am trying to create a top horizontal scroll, which I have done and its working fine. But I need to assign some events on the window.resize.
I created a component such as
<template>
<div class="top-scrollbar">
<div class="top-horizontal-scroll"></div>
</div>
</template>
<style scoped>
.top-scrollbar {
width: 100%;
height: 20px;
overflow-x: scroll;
overflow-y: hidden;
margin-left: 14px;
.top-horizontal-scroll {
height: 20px;
}
}
</style>
<script>
export default {
mounted() {
document.querySelector("div.top-scrollbar").addEventListener('scroll', this.handleScroll);
document.querySelector("div.vuetable-body-wrapper").addEventListener('scroll', this.tableScroll);
},
methods: {
handleScroll () {
document.querySelector("div.vuetable-body-wrapper").scrollLeft = document.querySelector("div.top-scrollbar").scrollLeft
},
tableScroll() {
document.querySelector("div.top-scrollbar").scrollLeft = document.querySelector("div.vuetable-body-wrapper").scrollLeft
}
}
}
</script>
I am calling it above the table such as <v-horizontal-scroll />
I created a mixin as
Vue.mixin({
methods: {
setScrollBar: () => {
let tableWidth = document.querySelector("table.vuetable").offsetWidth;
let tableWrapper = document.querySelector("div.vuetable-body-wrapper").offsetWidth;
document.querySelector("div.top-horizontal-scroll").style.width = tableWidth + "px";
document.querySelector("div.top-scrollbar").style.width = tableWrapper + "px"
}
}
})
And I am calling it when the user component on which Vuetable is being created
beforeUpdate() {
document.addEventListener("resize", this.setScrollBar());
},
mounted() {
this.$nextTick(function() {
window.addEventListener('resize', this.setScrollBar);
this.setScrollBar()
});
},
I want to understand how this resizing event working.
If I change even a single thing in the above code. I am starting to have issues.
Either it doesn't set the width of scroll main div correctly or even this.setScrollBar don't work on resizing.
I am not clear what is the logic behind this and how it is working?

VueJS - onclick to make active on new Array entry not working

https://codepen.io/donnieberry97/pen/GGKQRN
var demo = new Vue({
el: '#main',
data: {
services: [
{
name: 'Item 1',
price: 200,
active: true
},
{
name: 'Item 2',
price: 500,
active: false
},
{
name: 'Item 3',
price: 700,
active: false
}
]
},
methods: {
addItem: function() {
var newItem= {
name:this.name,
price:this.price
};
this.services.push(newItem);
this.name="";
this.price="";
toggleActive();
},
toggleActive: function(f) {
f.active = !f.active;
},
total: function(){
var total=0;
this.services.forEach(function(f){
if(f.active){
total+=f.price;
}
});
return total;
}
}
});
When you use the input to add a new entry to the services array, upon clicking it afterwards, the active tag does not get applied to the new entry. It should turn blue and add to the total price but only the hover state works.
I've modified you code at method 'addItem' and use computed property total instead total method,have a look:
var demo = new Vue({
el: '#main',
data: {
services: [
{
name: 'Item 1',
price: 200,
active: true
},
{
name: 'Item 2',
price: 500,
active: false
},
{
name: 'Item 3',
price: 700,
active: false
}
]
},
computed: {
total () {
return this.services.reduce((last,item)=>last + parseInt(item.price) * item.active,0)
}
},
methods: {
addItem: function() {
var newItem= {
name:this.name,
price:this.price,
active: true
};
this.services.push(newItem);
this.name="";
this.price="";
},
toggleActive: function(f) {
f.active = !f.active;
}
}
});
* {
padding: 0;
margin: 0;
}
body{
font-family: 'Roboto', sans-serif !important;
}
h3 {
text-align:center;
padding: 2em 0em;
}
h5 {
padding: 1.5em 0.5em;;
box-sizing:border-box;
}
.container {
width:600px;
margin: 0 auto;
}
ul {
list-style:none;
}
li {
color:black;
border:1px solid #eeeeee;
padding:0.5em;
border-left: 5px solid #2196F3;
height:30px;
line-height:30px;
transition: 0.4s ease;
}
.active {
background-color:#2196F3;
color:white;
transition: 0.3s;
transition: 0.4s ease;
}
.active:hover {
background-color:#2196F3;
}
li:hover {
background-color:#82c4f8;
transition: 0.4s ease;
cursor:pointer;
}
span {
float:right;
}
#main {
box-shadow: 0 19px 38px rgba(0,0,0,0.0), 0 6px 12px rgba(0,0,0,0.22)
}
.text-center {
text-align:center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.27/vue.min.js"></script>
<div class="container">
<div id="main">
<div class="header"><h3>Click the services you wish to have:</div>
<ul>
<li class="group-item" v-for="service in services" v-on:click="toggleActive(service)" v-bind:class="{'active': service.active}">{{service.name}} <span>{{service.price | currency}}</span></li>
</ul>
<h5>Total is: {{total | currency}}</h5>
<input type="text" v-model="name" placeholder="name">
<input type="text" v-model="price" placeholder="price">
<button v-on:click="addItem()">Add Item</button>
</div>
</div>
The main issue that you're running into is you're calling an undefined function and not passing a parameter into your toggleActive function.
Since toggleActive is a Vue method, you'll need to use this to reference it correctly and use the function from your Vue instance; once that problem is fixed, you'll need to pass in the item that you're wanting to toggle, because the way that function is written it requires a parameter to update active status.
Here's how you could update your addItem function to get it working:
addItem: function() {
var newItem= {
name:this.name,
price:this.price,
active: false,
};
this.services.push(newItem);
this.name="";
this.price="";
this.toggleActive(this.services[this.services.length - 1]);
},
Also notice that I added the active property during item creation so that Vue treats this as a reactive property. Otherwise, your item will be stuck in the active state (after toggling it) and cannot become inactive on click. You could change this to just be active: true during creation (and remove the call to make it active completely) if all new items are supposed to be active on creation. I didn't do that, though, as I wanted to show how to fix the call to toggleActive.
You can view a forked and updated Codepen here if you'd like to see the code in a fully working state.