Create Konvajs Shapes and Connections creating dynamically based on button click events - vue.js

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>

Related

Why does transition-group animate opacity but not animate height?

the code below animates the element you click on, but I want it to smoothly decrease the height to zero as well. Unfortunately, the height does not change, but everything works with opacity.
height: 0 !improtant; //does not help to solve the problem
<template>
<transition-group name="msgAnimation" tag="div">
<div v-for="(obj, i) in messages" :key="obj.key" class="wrapper">
<div class="wrapper__block" #click="messages.splice(i, 1)">
{{ obj.msg }}
</div>
</div>
</transition-group>
</template>
<style lang="css">
.msgAnimation-enter-active,
.msgAnimation-leave-active {
transition: all 5s;
}
.msgAnimation-enter,
.msgAnimation-leave-to {
height: 0;
opacity: 0;
}
.wrapper {
width: 100%;
height: 9vmin;
}
.wrapper__block {
background: green;
height: 9vmin;
width: 100%;
}
</style>
<script>
export default {
name: "HelloWorld",
data() {
return {
totalAmount: 0,
messages: [{ key: 0, msg: "Are u hacker" }],
};
},
};
</script>
Your CSS rules was declared after your animation
const example = {
data() {
return {
totalAmount: 0,
messages: [{
key: 0,
msg: "Are u hacker"
},
{
key: 1,
msg: "Are u hacker"
}
],
};
},
};
const app = new Vue(example);
app.$mount("#app");
.msgAnimation-enter-active,
.msgAnimation-leave-active {
transition: all 5s;
}
.wrapper {
width: 100%;
height: 9vmin;
overflow:hidden;
}
.wrapper__block {
background: green;
width: 100%;
}
.msgAnimation-enter,
.msgAnimation-leave-to {
opacity: 0;
height: 0;
}
<script src="https://unpkg.com/vue/dist/vue.min.js"></script>
<div id="app">
<transition-group name="msgAnimation" tag="div">
<div v-for="(obj, i) in messages" :key="obj.key" class="wrapper">
<div class="wrapper__block" #click="messages.splice(i, 1)">
{{ obj.msg }}
</div>
</div>
</transition-group>
</div>
You're not limiting overflow of .wrapper, and you have a height specified in .wrapper__block. Thus, even if .wrapper goes smoothly to 0, its child will not.
Setting height: 100%; on .wrapper__block, or setting overflow: hidden on .wrapper should do the trick.
<template>
<transition-group name="msgAnimation" tag="div">
<div v-for="(obj, i) in messages" :key="obj.key" class="wrapper">
<div class="wrapper__block" #click="messages.splice(i, 1)">
{{ obj.msg }}
</div>
</div>
</transition-group>
</template>
<style lang="css">
.msgAnimation-enter-active,
.msgAnimation-leave-active {
transition: all 5s;
}
.msgAnimation-enter,
.msgAnimation-leave-to {
height: 0;
opacity: 0;
}
.wrapper {
width: 100%;
height: 9vmin;
}
.wrapper__block {
background: green;
height: 100%;
width: 100%;
}
</style>
<script>
export default {
name: "HelloWorld",
data() {
return {
totalAmount: 0,
messages: [{ key: 0, msg: "Are u hacker" }],
};
},
};
</script>
Alternatively, if you don't mind distortion during the animation, it's a lot more performant to animate transform: scaleY(0), as transform and opacity are applied at the Composition step in CSS, you prevent a lot of in-between style calculations, making your app noticeably faster whn you have several thousand messages.

How to make swiper navigation disabled on activeIndex == 1

i try to make design like image below.
i set the initial slide on "1". i want to make the swiper navigation disable on index "1".
can you please to figure it out how?
i already try so many answer on stack overflow, but still can't.
here's my code.
//Initialize Swiper
var swiper = new Swiper('.swiper-container.other-adventure', {
autoHeight: true,
initialSlide: 1,
slidesPerView: 4,
centeredSlides: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
breakpoints: {
480: {
slidesPerView: 1,
spaceBetween: 20,
},
768: {
slidesPerView: 3,
spaceBetween: 20,
},
1024: {
slidesPerView: 4,
spaceBetween: 15,
},
2560: {
slidesPerView: 4,
spaceBetween: 15,
},
}
});
It's hard to know what you're trying to do (Your design and the code you added not match).
Anyway, this is the outline for "doing something" on some index.
swiper API slideChange & realIndex
One way is simply to use swiper API slideChange event and realIndex property and On slide change if realIndex == 0... do something.
Docs:
https://swiperjs.com/swiper-api#methods-and-properties
https://swiperjs.com/swiper-api#events
** First item has an index of 0.
<script>
/* hide left arrow on load (Another option is to put this code inside init event) */
var arrow = document.getElementsByClassName('swiper-button-prev')[0];
arrow.style.display = 'none';
/* Swiper API - if index = 1 hide left arrow / otherwise show */
swiper.on('slideChange', function() {
var realIndex = swiper.realIndex;
if (realIndex == 0) {
console.log(realIndex + " - hide arrow");
arrow.style.display = 'none';
} else {
console.log(realIndex + " - show arrow");
arrow.style.display = 'block';
}
});
</script>
Full code example:
html, body {
position: relative;
height: 100%;
margin: 0;
padding: 0;
height: 500px;
}
.swiper-container {
width: 100%;
height: 100px;
}
.swiper-slide {
text-align: center;
font-size: 24px;
background: black;
color: white;
display: flex;
align-items: center;
justify-content: center;
}
.swiper-slide-active{
background: red;
}
<!-- Link Swiper's CSS -->
<link rel="stylesheet" href="https://unpkg.com/swiper#6.2.0/swiper-bundle.min.css">
</head>
<body>
<!-- Swiper -->
<div class="swiper-container">
<div class="swiper-wrapper">
<div class="swiper-slide">Slide 1</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
<div class="swiper-slide">Slide 4</div>
<div class="swiper-slide">Slide 5</div>
<div class="swiper-slide">Slide 6</div>
</div>
<!-- Add Arrows -->
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
</div>
<!-- Swiper JS -->
<script src="https://unpkg.com/swiper#6.2.0/swiper-bundle.min.js"></script>
<!-- Initialize Swiper -->
<script>
var swiper = new Swiper('.swiper-container', {
initialSlide: 1,
slidesPerView: 3,
spaceBetween: 20,
centeredSlides: true,
navigation: {
nextEl: '.swiper-button-next',
prevEl: '.swiper-button-prev',
},
});
</script>
<script>
/* hide left arrow by deafult */
var arrow = document.getElementsByClassName('swiper-button-prev')[0];
arrow.style.display = 'none';
/* Swiper API - if index = 1 hide left arrow / otherwise show */
swiper.on('slideChange', function() {
var realIndex = swiper.realIndex;
if (realIndex == 0) {
console.log("real index:" + realIndex + " - hide arrow");
arrow.style.display = 'none';
} else {
console.log("real index:" + realIndex + " - show arrow");
arrow.style.display = 'block';
}
});
</script>
Show/hide by simple js:
https://gomakethings.com/how-to-show-and-hide-elements-with-vanilla-javascript/

How to trigger function on viewport visible with Vue viewport plugin

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.

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.

Vue Accordion with transition

I'm trying to integrate the Accordion component with a body transition, but without success :( . All is working as well except the animation.
template:
<div class="accordion">
<div class="accordion-title" #click="isOpen = !isOpen" :class="{'is-open': isOpen}">
<span>{{title}}</span>
<i class="ic ic-next"></i>
</div>
<div class="accordion-body" :class="{'is-open': isOpen}">
<div class="card">
<slot name="body"></slot>
</div>
</div>
</div>
component:
props: {
title: {
type: String,
default: 'Title'
}
},
data() {
return {
isOpen: false
}
}
And styles:
.accordion-body {
font-size: 1.3rem;
padding: 0 16px;
transition: .3s cubic-bezier(.25,.8,.5,1);
&:not(.is-open) {
display: none;
height: 0;
overflow: hidden;
}
&.is-open {
height: auto;
// display: block;
padding: 16px;
}
}
.card {
height: auto;
}
I tried to use <transition> but it doesn't work with height or display properties.
Help please!
display:none will remove your content and avoid the animation, you should trick with opacity, overflow:hidden and height, but you ll be forced to do a method for that.
For example (not tested, but inspiring):
in template:
<div class="accordion" #click="switchAccordion" :class="{'is-open': isOpen}">
<div class="accordion-title">
<span>{{title}}</span>
<i class="ic ic-next"></i>
</div>
<div class="accordion-body">
<p></p>
</div>
</div>
in component (add a method):
methods: {
switchAccordion: function (event) {
let el = event.target
this.isOpen = !this.isOpen // switch data isOpen
if(this.isOpen) {
let childEl1 = el.childNodes[1]
el.style.height = childEl1.style.height
} else {
let childEl2 = el.childNodes[2]
el.style.height = childE2.style.height // or .clientHeight + "px"
}
}
}
in style:
.accordion {
transition: all .3s cubic-bezier(.25,.8,.5,1);
}
.accordion-body {
font-size: 1.3rem;
padding: 0 16px;
opacity:0
}
.is-open .accordion-body {
opacity:0
}
In this case, your transition should work as you want.
The javascript will change the height value and transition transition: all .3s cubic-bezier(.25,.8,.5,1); will do the animation