Set element height based on parent height in vue js on page load - vue.js

I want to set the height of a vue component (to be specific it is this -> https://github.com/jbaysolutions/vue-grid-layout).
The height of which (in vue-grid-layout -> grid-item) should be same as of its parent.
And this also should be only in page load time. So how this can be achieved in vue js?
I don't want to do this using CSS. I need height in pixels. as to vue-grid-layout -> item height needs in pixel initially. as it is resizable, it can be changed afterwards.

it would be easier to provide an accurate answer with some example html (your actual code), but the following should work in general.
export default {
...
data() {
return {
parentHeight: 0
}
},
mounted() {
this.parentHeight = this.$parent.$el.offsetHeight;
}
...
}
So in the mounted lifecycle hook you can read the height of the parent and then set it where ever you need it.

No need for advanced javascript to calculate the height, just use styling:
height: 100%;
Demo:
.parent {
resize: both;
overflow: auto;
height: 100px;
display: block;
width: 100px;
border: 1px solid black;
}
.child {
height: 100%;
background: pink;
}
<div class="parent">
</div>
<div class="parent">
<div class="child">
I'm a child!
</div>
</div>

I found a solution to fix the grid-layout height depending on available space. For that I have used folowing props: max-rows, row-height, margins, autoSize=false
And ResizeObserver which will adjust grid-layout row-height according to available height after window resize. Also be aware that I used some Bootstrap classes for styling
<template>
<div class="flex-grow-1 w-100">
<grid-layout
:layout="layout"
:col-num="colCount"
:maxRows="rowCount"
:row-height="rowHeight"
:autoSize="false"
:is-draggable="true"
:is-resizable="true"
:is-mirrored="false"
:preventCollision="true"
:vertical-compact="false"
:margin="margin"
:use-css-transforms="true"
>
<grid-item
v-for="item in layout"
class="bg-primary"
:x="item.x"
:y="item.y"
:w="item.w"
:h="item.h"
:i="item.i"
:key="item.i"
>
<span class="remove" #click="removeItem(item.i)">x</span>
</grid-item>
</grid-layout>
</div>
</template>
<script lang="ts">
import { defineComponent } from '#vue/runtime-core';
interface GridItem {
x: number;
y: number;
w: number;
h: number;
i: string;
}
interface State {
layout: GridItem[];
index: number;
colCount: number;
rowCount: number;
rowHeight: number;
observer: null | ResizeObserver;
margin: number[];
}
export default defineComponent({
name: 'VideoWall',
data(): State {
return {
layout: [
{ x: 0, y: 0, w: 2, h: 2, i: '0' },
{ x: 2, y: 0, w: 2, h: 4, i: '1' },
{ x: 4, y: 0, w: 2, h: 5, i: '2' },
{ x: 6, y: 0, w: 2, h: 3, i: '3' },
{ x: 8, y: 0, w: 2, h: 3, i: '4' },
],
index: 0,
colCount: 36,
rowCount: 36,
rowHeight: 40,
margin: [5, 5],
observer: null,
};
},
mounted() {
this.observer = new ResizeObserver(this.onResize);
if (this.$el instanceof Element) {
this.observer.observe(this.$el);
} else {
console.log('VideoWall: Failed to bind observer');
}
},
unmounted() {
if (this.observer) {
this.observer.disconnect();
}
},
methods: {
onResize(entries: ResizeObserverEntry[]): void {
this.rowHeight =
Math.floor(entries[0].contentRect.height / this.rowCount) -
this.margin[1];
},
},
});
</script>

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 animate svg elements in a for loop with vue?

working with vue-konva (svg based canvas library) right now. I'm trying to animate all shapes which are defined by a v-loop. While trying to use the Konva Animation library functions, I'm getting "Cannot read property 'getNode' of undefined" error. I'm assuming this is due to the fact that a ref has to have one specific element and not be adressed inside a v-for loop. How can I simultaneaously animate all the polygons?
SVG / canvas element:
<v-regular-polygon
v-for="el in results"
ref="hexagon"
:key="el.index"
:config="{
x: 200 * Math.abs(el.land),
y: 200,
sides: 6,
radius: 20,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
}"
/>
function responsible for animation
mounted() {
this.fetchTemperature()
const vm = this
const amplitude = 100
const period = 5000
// in ms
const centerX = vm.$refs.stage.getNode().getWidth() / 2
const hexagon = this.$refs.hexagon.getNode()
// example of Konva.Animation
const anim = new Konva.Animation(function (frame) {
hexagon.setX(amplitude * Math.sin((frame.time * 2 * Math.PI) / period) + centerX)
}, hexagon.getLayer())
anim.start()
},
You can set a unique ref to every polygon by adding an index.
<v-regular-polygon
v-for="(el, i) in results"
:ref="`hexagon_${i}`"
...
Here's an example:
<template>
<div id="app">
<v-stage :config="configKonva">
<v-layer>
<v-circle
v-for="(item, i) in items"
:config="item"
:key="i"
:ref="`circle_${i}`"
></v-circle>
</v-layer>
</v-stage>
</div>
</template>
<script>
import Konva from "konva";
export default {
name: "App",
data() {
return {
items: [
{
x: 30,
y: 100,
radius: 20,
fill: "red",
stroke: "black",
strokeWidth: 2,
},
{
x: 100,
y: 100,
radius: 20,
fill: "red",
stroke: "black",
strokeWidth: 2,
},
],
configKonva: {
width: 200,
height: 200,
},
};
},
mounted() {
for (let i = 0; i < this.items.length; i++) {
const node = this.$refs[`circle_${i}`][0].getNode();
const period = 1000;
const amplitude = 10;
const anim = new Konva.Animation((frame) => {
node.setX(
amplitude * Math.sin((frame.time * 2 * Math.PI) / period) +
this.items[i].x
);
}, node.getLayer());
anim.start();
}
},
};
</script>
Codesandbox

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.

How to get graph from sigmajs to display in html page

I am new to Aurelia and have some background in javascript. I am using CLI to build and run my project. Dwayne Charrington has guided me on the integration of sigmajs into Aurelia. Thanks, Dwayne!
I have installed sigma into the project through
npm install sigma --save
I have prepended the sigma.min.js to aurelia_project/aurelia.json.
"prepend": [
"node_modules/bluebird/js/browser/bluebird.core.js",
"node_modules/requirejs/require.js",
"node_modules/sigma/build/sigma.min.js"
]
I am using the attached() lifecycle method to get the ts and html to work together for integrating sigmajs as suggested by Dwayne.
-- sigmajs.html
<template>
<require from="./sigmajs.css"></require>
<section>
<div class="col-lg-4 col-lg-offset-4">
<h1> An </h1>
<div id="container1"></div>
</div>
<div class="col-lg-4 col-lg-offset-4">
<button click.delegate="refreshUI()"> Refresh UI </button>
</div>
</section>
</template>
--sigmajs.ts
export class Sigmajs {
s;
refreshUI() {
this.s.refresh();
this.s.graph.nodes().forEach(function(n) {
console.log(n);
});
}
attached() {
this.s = new sigma({
container: 'container1'
});
this.s.graph.addNode({
id: 'n0',
label: 'Hello',
x: 10,
y: 10,
size: 1,
color: '#f00'
}).addNode({
id: 'n1',
label: 'World !',
x: 100,
y: 10,
size: 1,
color: '#00f'
}).addEdge({
id: 'e0',
source: 'n0',
target: 'n1'
});
}
}
-- sigmajs.css
<style>
#container1 {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
</style>
I am unable to see the graph on the HTML page; even when I click the Refresh UI button. When I click the Refresh UI button, I do see the debug statements in the console corresponding to console.log(n) as
Object { label: "Hello", x: 10, y: 10, size: 1, color: "#f00", id: "n0", read_cam0:size: 8, read_cam0:x: -2.8125, read_cam0:y: 0, cam0:x: 195.1875, 2 more… } app-bundle.js:396:17
Object { label: "World !", x: 100, y: 10, size: 1, color: "#00f", id: "n1", read_cam0:size: 8, read_cam0:x: 2.8125, read_cam0:y: 0, cam0:x: 200.8125, 2 more… } app-bundle.js:396:17
thereby leading me to conclude that I am initializing sigmajs correctly; just not seeing the output (of a graph) on HTML
Thank you in advance for your advice.
Answering my own question
The issue was with the css NOT specifying the appropriate height, width with bootstrap also in picture. The following declaration in sigmajs.css shows the rather simple graph (2 nodes, one edge).
<style type="text/css">
body {
margin: 0;
}
#container2 {
position: absolute;
width: 100%;
height: 100%;
}
</style>
I am not a CSS guy; so this answer, while it works, may need some nuances for the graph to look better.
#LStarky It does not seem necessary to do sigma import. I believe that vendor-bundle.js also has sigma due to prepend