Complex SVG animation in ReactJS - javascript

I am using React to build an animated intro that comprises three taglines paired with animated SVG icons.
I opted for TweenMax to manage the SVG animations, due to the solid cross-browser support it offers. This also would allow me to perform a simple morph of the d attribute on <path> elements. I am combining the aforementioned with ReactTransitionGroup
However, I ran into the following problems while trying to make TweenMax and React play nice:
While opting for component state, componentWillReceiveProps is somehow called twice in console. This would mean, my animation method would be called twice. What is causing this and might storing the tag index using Redux be a better option in this use case?
The way that I am animating currently seems very crude, e.g. repeating this.refs and findDOMNode() repeatedly. Surely there must be a better way? In which ways could the structure of my code be improved?
I am completely stumped and would love to be pointed in the right direction.
Kind regards, Jason
Code
html,
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
body {
background: rgb(240, 90, 48);
font: bold 1em sans-serif;
color: rgb(255, 255, 255);
text-align: center;
}
.reveal-wrap {
position: absolute;
width: 200px;
height: 200px;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
overflow: hidden;
}
.reveal-icon {
width: 100px;
height: 100px;
margin: 0 auto 2em auto;
}
.reveal-icon svg {
width: 100%;
height: 100%;
}
.reveal-icon .fill {
fill: rgb(251, 163, 10);
}
.reveal-icon .mask {
fill: rgb(240, 90, 48);
}
.reveal-icon .stroke {
stroke: rgb(251, 163, 10);
stroke-linecap: round;
stroke-width: 5;
}
.reveal-text {
position: absolute;
width: 100%;
}
.switch-locale {
position: fixed;
top: 1em;
left: 1em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.25/browser.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/gsap/latest/TweenMax.min.js"></script>
<script src="https://fb.me/react-with-addons-0.13.3.min.js"></script>
<div id="react-root"></div>
<script type="text/babel">
const ReactTransitionGroup = React.addons.TransitionGroup
const UI_TEXT = {
EN_US: {
REVEAL: [
{ ID: 0, TEXT: 'Tagline 1' },
{ ID: 1, TEXT: 'Tagline 2' },
{ ID: 2, TEXT: 'Tagline 3' }
]
},
NL_NL: {
REVEAL: [
{ ID: 0, TEXT: 'Slagzin 1' },
{ ID: 1, TEXT: 'Slagzin 2' },
{ ID: 2, TEXT: 'Slagzin 3' }
]
}
}
class Reveal extends React.Component {
constructor() {
super();
this.state = {
index: 0,
locale: 'EN_US'
}
}
nextTagline() {
this.setState({index: this.state.index + 1})
console.log('this.state.index # ' + this.state.index)
}
switchLocale() {
let locale = (this.state.locale === 'EN_US') ? 'NL_NL' : 'EN_US'
this.setState({locale})
}
render() {
return (
<ReactTransitionGroup className='reveal-wrap' component='div'>
<RevealIcon tag={this.state.index} nextTag={() => this.nextTagline()} />
<RevealText tag={this.state.index} locale={this.state.locale} key={this.state.index} />
<SwitchLocale switchLocale={() => this.switchLocale()} />
</ReactTransitionGroup>
)
}
}
class RevealText extends React.Component {
fadeIn(callback, delay) {
TweenLite.fromTo(React.findDOMNode(this), 0.5,
{
y: '100px',
opacity: 0
},
{
y: 0,
delay: delay,
opacity: 1,
ease: Quad.easeOut,
onComplete: callback,
onCompleteScope: this
}
)
}
fadeOut(callback, delay) {
TweenLite.fromTo(React.findDOMNode(this), 0.5,
{
y: 0,
opacity: 1
},
{
y: '+=100px',
delay: delay,
opacity: 0,
ease: Quad.easeIn,
onComplete: callback,
onCompleteScope: this
}
)
}
componentWillAppear(callback) {
//console.log('RevealText will appear')
this.fadeIn(callback, 1)
}
componentDidAppear() {
//console.log("RevealText did appear")
}
componentWillLeave(callback) {
this.fadeOut(callback, 0)
}
componentDidLeave() {
//console.log('RevealText did leave')
}
componentWillEnter(callback) {
this.fadeIn(callback, 1)
}
componentDidEnter() {
//console.log("RevealText did enter")
}
render() {
return (
<div className='reveal-text'>
{ UI_TEXT[this.props.locale].REVEAL[this.props.tag].TEXT }
</div>
)
}
}
class RevealIcon extends React.Component {
componentWillAppear(callback) {
const HAND_1 = [React.findDOMNode(this.refs.HAND_1),
React.findDOMNode(this.refs.HAND_1_MASK),
React.findDOMNode(this.refs.HAND_1_THUMB)]
const HAND_2 = [React.findDOMNode(this.refs.HAND_2),
React.findDOMNode(this.refs.HAND_2_MASK)]
const HAND_3 = React.findDOMNode(this.refs.HAND_LINES)
const HAND_4 = [React.findDOMNode(this.refs.HAND_LINES_1),
React.findDOMNode(this.refs.HAND_LINES_2),
React.findDOMNode(this.refs.HAND_LINES_3),
React.findDOMNode(this.refs.HAND_LINES_4),
React.findDOMNode(this.refs.HAND_LINES_5),
React.findDOMNode(this.refs.HAND_LINES_6),
React.findDOMNode(this.refs.HAND_LINES_7),
React.findDOMNode(this.refs.HAND_LINES_8)]
let anim = new TimelineMax({
delay: 2,
onComplete: this.props.nextTag,
onCompleteScope: this
})
anim.fromTo(HAND_1, 0.5,
{
y: '-=100px',
x: '+=100px',
opacity: 0
},
{
y: 0,
x: 0,
opacity: 1,
ease: Quad.easeOut
})
.fromTo(HAND_2, 0.5,
{
y: '-=100px',
x: '-=100px',
opacity: 0
},
{
y: 0,
x: 0,
opacity: 1,
ease: Quad.easeOut
}, '-=0.20')
.fromTo(HAND_3, 0.75,
{
scaleX: 0.5,
scaleY: 0.5,
transformOrigin: '50% 50%'
},
{
scaleX: 1,
scaleY: 1,
ease: Quad.easeOut
})
.fromTo(HAND_4, 0.5,
{
opacity: 0,
},
{
opacity: 1,
ease: Quad.easeOut
}, '-=0.75')
.fromTo(HAND_4, 1,
{
'stroke-dasharray': '25px',
'stroke-dashoffset': '0px'
},
{
'stroke-dasharray': '25px',
'stroke-dashoffset': '25px',
ease: Power3.easeOut
}, '-=0.75')
.set({}, {}, '+=1')
// .set is used to lengthen the animation by 1 second
}
componentWillReceiveProps(nextProps) {
console.log('RevealIcon will receive props', 'nextProps.tag: ' + nextProps.tag)
if(nextProps.tag === 1){
// Animation code / reference to method here
} else if (nextProps.tag === 2) {
// Animation code / reference to method here
} else if (nextProps.tag === 3) {
// Animation code / reference to method here
}
}
render() {
return (
<div className='reveal-icon' >
<svg height="200" width="200" viewBox="0, 0, 200, 200">
<path ref="HAND_1" className="fill" d="M146.8,79.9l-55.2,55.2c-1.8,1.8-4.8,1.8-6.7,0c-1.8-1.8-1.8-4.8,0-6.7h0l18.4-18.4l-3.3-3.3
l-18.4,18.4c-0.9,0.9-2.1,1.4-3.3,1.4s-2.5-0.5-3.3-1.4c-0.9-0.9-1.4-2.1-1.4-3.3c0-1.3,0.5-2.5,1.4-3.3L93.3,100L90,96.7
l-18.4,18.4c-1.8,1.8-4.8,1.8-6.7,0c-1.8-1.8-1.8-4.8,0-6.7l41.8-41.8l-3.3-3.3L61.5,105c-3.7,3.7-3.7,9.7,0,13.4
c1.8,1.8,4.3,2.8,6.7,2.8c0.2,0,0.4,0,0.6,0c0,0.2,0,0.4,0,0.6c0,2.5,1,4.9,2.8,6.7c1.8,1.8,4.2,2.8,6.7,2.8c0.2,0,0.4,0,0.6,0
c-0.2,2.6,0.7,5.3,2.7,7.3c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8l55.2-55.2L146.8,79.9z"/>
<path ref="HAND_2_MASK" className="mask" d="M138.5,105l-22.7-22.7L83.3,49.8L49.8,83.3l32.5,32.5l22.7,22.7
c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8c2-2,2.9-4.7,2.7-7.3c0.2,0,0.4,0,0.6,0c2.5,0,4.9-1,6.7-2.8
c1.8-1.8,2.8-4.2,2.8-6.7c0-0.2,0-0.4,0-0.6c0.2,0,0.4,0,0.6,0c2.4,0,4.8-0.9,6.7-2.8c1.8-1.8,2.8-4.2,2.8-6.7
C141.2,109.2,140.2,106.8,138.5,105z"/>
<path ref="HAND_2" className="fill" d="M138.5,105L83.3,49.8l-3.3,3.3l55.2,55.2c0.9,0.9,1.4,2.1,1.4,3.3c0,1.3-0.5,2.5-1.4,3.3
c-1.8,1.8-4.8,1.8-6.7,0L110,96.7l-3.3,3.3l18.4,18.4c0.9,0.9,1.4,2.1,1.4,3.3c0,1.3-0.5,2.5-1.4,3.3c-0.9,0.9-2.1,1.4-3.3,1.4
s-2.5-0.5-3.3-1.4L100,106.7l-3.3,3.3l18.4,18.4c1.8,1.8,1.8,4.8,0,6.7c-1.8,1.8-4.8,1.8-6.7,0L53.2,79.9l-3.3,3.3l55.2,55.2
c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8c2-2,2.9-4.7,2.7-7.3c0.2,0,0.4,0,0.6,0c2.5,0,4.9-1,6.7-2.8
c1.8-1.8,2.8-4.2,2.8-6.7c0-0.2,0-0.4,0-0.6c0.2,0,0.4,0,0.6,0c2.4,0,4.8-0.9,6.7-2.8c1.8-1.8,2.8-4.2,2.8-6.7
C141.2,109.2,140.2,106.8,138.5,105z"/>
<path ref="HAND_1_MASK" className="mask" d="M116.7,49.8l-5,5l-3.3-3.3c-1.8-1.8-4.2-2.8-6.7-2.8c-2.5,0-4.9,1-6.7,2.8
L73.2,73.2c-3.7,3.7-3.7,9.7,0,13.4c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8l20.1-20.1l5-5l8.4-8.4L116.7,49.8z"/>
<path ref="HAND_1_THUMB" className="fill" d="M116.7,49.8l-5,5l-3.3-3.3l0,0c-1.8-1.8-4.2-2.8-6.7-2.8c-2.5,0-4.9,1-6.7,2.8
L73.2,73.2c-3.7,3.7-3.7,9.7,0,13.4c1.8,1.8,4.3,2.8,6.7,2.8c2.4,0,4.8-0.9,6.7-2.8l20.1-20.1l-3.3-3.3L83.3,83.3
c-1.8,1.8-4.8,1.8-6.7,0s-1.8-4.8,0-6.7l21.7-21.7c0.9-0.9,2.1-1.4,3.3-1.4c1.3,0,2.5,0.5,3.3,1.4l6.7,6.7l8.4-8.4L116.7,49.8z"/>
<g ref="HAND_LINES">
<line ref="HAND_LINES_8" className="stroke" x1="32.6" y1="32.6" x2="49.4" y2="49.4"/>
<line ref="HAND_LINES_7" className="stroke" x1="4.7" y1="100" x2="28.4" y2="100"/>
<line ref="HAND_LINES_6" className="stroke" x1="32.6" y1="167.4" x2="49.4" y2="150.6"/>
<line ref="HAND_LINES_5" className="stroke" x1="100" y1="195.3" x2="100" y2="171.6"/>
<line ref="HAND_LINES_4" className="stroke" x1="167.4" y1="167.4" x2="150.6" y2="150.6"/>
<line ref="HAND_LINES_3" className="stroke" x1="195.3" y1="100" x2="171.6" y2="100"/>
<line ref="HAND_LINES_2" className="stroke" x1="167.4" y1="32.6" x2="150.6" y2="49.4"/>
<line ref="HAND_LINES_1" className="stroke" x1="100" y1="4.7" x2="100" y2="28.4"/>
</g>
</svg>
</div>
)
}
}
class SwitchLocale extends React.Component {
render() {
return (
<button className='switch-locale' onClick={this.props.switchLocale}>
Switch locale
</button>
)
}
}
React.render(<Reveal/>, document.getElementById('react-root'))
</script>
On the left, the animation I currently have created in React, on the right, the icons I still have to implement in the same manner.

Related

How to make particles js to disappear after banner section end in react

I built a banner section with particles js background. and I want start new section but the particles js background is fixed position important. I want to make the particles disappear after the banner end .
I opend the developer tools and this is the code of canvas:
<canvas data-generated="false" style="width: 100% !important; height: 100% !important; position: fixed !important; z-index: -1 !important; top: 0px !important; left: 0px !important; background-color: rgb(0, 0, 0); pointer-events: none;" aria-hidden="true" width="133" height="640"></canvas>
Banner.js code:
import { ParticlesBg } from "./ParticlesBg"
import './banner.css'
export default function Banner() {
return (
<section className="banner">
<ParticlesBg className="bg" />
<main>
<h1>Hello World</h1>
</main>
</section>
)
}
ParticlesBg.js code:
import { useCallback } from "react";
import Particles from "react-tsparticles";
import { loadFull } from "tsparticles";
export function ParticlesBg() {
const particlesInit = useCallback(async engine => {
console.log(engine);
// you can initiate the tsParticles instance (engine) here, adding custom shapes or presets
// this loads the tsparticles package bundle, it's the easiest method for getting everything ready
// starting from v2 you can add only the features you need reducing the bundle size
await loadFull(engine);
}, []);
const particlesLoaded = useCallback(async container => {
await console.log(container);
}, []);
return (
<Particles
id="tsparticles"
init={particlesInit}
loaded={particlesLoaded}
options={{
fullScreen: {
"enable": true,
"zIndex": -1,
},
background: {
color: {
value: "#000000",
},
},
fpsLimit: 60,
interactivity: {
events: {
onClick: {
enable: false,
mode: "push",
},
onHover: {
enable: false,
mode: "repulse",
},
resize: true,
},
modes: {
push: {
quantity: 4,
},
repulse: {
distance: 200,
duration: 0.4,
},
},
position: "absolute"
},
particles: {
color: {
value: "#ffffff",
},
links: {
color: "#ffffff",
distance: 150,
enable: true,
opacity: 0.5,
width: 1,
},
collisions: {
enable: true,
},
move: {
directions: "none",
enable: true,
outModes: {
default: "bounce",
},
random: false,
speed: 1,
straight: false,
},
number: {
density: {
enable: true,
area: 800,
},
value: 80,
},
opacity: {
value: 0.5,
},
shape: {
type: "circle",
},
size: {
value: { min: 1, max: 5 },
},
},
detectRetina: true,
}}
/>
);
}
CSS code:
.banner{
width: 100%;
height: 100vh;
}
.banner h1 {
font-size: 60px;
color: white;
}
#particles-canvas {
position: absolute !important;
}
In CSS code:
section:not(.banner) {
background-color: white;
}

Nuxt js Custom cursor event listener not working after route change

I'm currently building a new website for our studio but can't get the custom cursor to work properly. Here's a custom cursor built with gsap and the result was great except when I navigate to another route and back to the home page, the mouseover event stops working and I can't find a reason why.
What could be causing this and how could this be fixed?
Thank you in advance!
Here's the CustomCursor component:
<template>
<div class="custom-cursor">
<div id="cursor-big" class="custom-cursor__ball custom-cursor__ball--big"></div>
<div id="cursor-small" class="custom-cursor__ball custom-cursor__ball--small"></div>
</div>
</template>
<script>
import gsap from "gsap";
export default {
props: {
hoverClass: {
type: String,
default: 'cursorHover'
}
},
mounted () {
const cursorBig = document.getElementById('cursor-big'),
cursorSmall = document.getElementById('cursor-small'),
links = document.getElementsByTagName("a"),
withClassHover = document.getElementsByClassName(this.hoverClass),
withHover = [...links, ...withClassHover];
// Event Listeners
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mousedown", onMouseHover);
document.addEventListener("mouseup", onMouseHoverOut);
document.addEventListener("mouseenter", () => {
cursorBig.style.opacity = 1;
cursorSmall.style.opacity = 1;
});
document.addEventListener("mouseleave", () => {
cursorBig.style.opacity = 0;
cursorSmall.style.opacity = 0;
});
withHover.forEach((element) => {
element.addEventListener("mouseover", onMouseHover);
element.addEventListener("mouseout", onMouseHoverOut);
})
// Event Handlers
function onMouseMove(e) {
cursorSmall.style.opacity = 1;
gsap.to(cursorBig, 0.4, {
x: e.clientX - 18.5,
y: e.clientY - 18.5
});
gsap.to(cursorSmall, 0.1, {
x: e.clientX - 4,
y: e.clientY - 4
});
}
function onMouseHover() {
gsap.to(cursorBig, 0.3, {
scale: 3,
});
}
function onMouseHoverOut() {
gsap.to(cursorBig, 0.3, {
scale: 1,
});
}
}
};
</script>
<style>
#media screen and (min-width:1100px) {
* {
cursor: none !important;
}
.custom-cursor__ball {
position: fixed;
top: 0;
left: 0;
mix-blend-mode: difference;
z-index: 99999;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease;
}
.custom-cursor__ball--big {
content: "";
width: 35px;
height: 35px;
background: white;
border-radius: 50%;
}
.custom-cursor__ball--small {
content: "";
width: 6px;
height: 6px;
background: #fff;
border-radius: 50%;
}
}
</style>
Moved from comments:
Issue: Elements with cursorHover class are not on the DOM after they have been removed when you route off somewhere else. Mounted only fires once.
Fix: Handle reinitiating your events onto the dom elements, and destroy your custom event handlers as the route changes.
<template>
<div class="custom-cursor">
<div
id="cursor-big"
class="custom-cursor__ball custom-cursor__ball--big"
></div>
<div
id="cursor-small"
class="custom-cursor__ball custom-cursor__ball--small"
></div>
</div>
</template>
<script>
import gsap from "gsap";
export default {
name: "CustomCursor",
props: {
hoverClass: {
type: String,
default: "cursorHover",
},
},
data() {
return {
cursorBig: null,
cursorSmall: null,
withHover: [],
};
},
watch: {
"$route.path"() {
console.log("route change");
this.destroy();
this.$nextTick(this.init);
},
},
mounted() {
console.log("mounted");
this.$nextTick(this.init);
},
beforeDestroy() {
console.log("beforeDestroy");
this.destroy();
},
methods: {
init() {
console.log("init");
setTimeout(() => {
this.cursorBig = document.getElementById("cursor-big");
this.cursorSmall = document.getElementById("cursor-small");
this.withHover = [
...document.getElementsByTagName("a"),
...document.getElementsByClassName(this.hoverClass),
];
this.withHover.forEach((element) => {
element.addEventListener("mouseover", this.onMouseHover);
element.addEventListener("mouseout", this.onMouseHoverOut);
});
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mousedown", this.onMouseHover);
document.addEventListener("mouseup", this.onMouseHoverOut);
document.addEventListener("mouseenter", this.onMouseEnter);
document.addEventListener("mouseleave", this.onMouseLeave);
}, 100);
},
destroy() {
console.log("destroy");
this.withHover.forEach((element) => {
element.removeEventListener("mouseover", this.onMouseHover);
element.removeEventListener("mouseout", this.onMouseHoverOut);
});
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mousedown", this.onMouseHover);
document.removeEventListener("mouseup", this.onMouseHoverOut);
document.removeEventListener("mouseenter", this.onMouseEnter);
document.removeEventListener("mouseleave", this.onMouseLeave);
},
onMouseEnter() {
this.cursorBig.style.opacity = 1;
this.cursorSmall.style.opacity = 1;
},
onMouseLeave() {
this.cursorBig.style.opacity = 0;
this.cursorSmall.style.opacity = 0;
},
onMouseMove(e) {
this.cursorSmall.style.opacity = 1;
gsap.to(this.cursorBig, 0.4, {
x: e.clientX - 18.5,
y: e.clientY - 18.5,
});
gsap.to(this.cursorSmall, 0.1, {
x: e.clientX - 4,
y: e.clientY - 4,
});
},
onMouseHover() {
gsap.to(this.cursorBig, 0.3, {
scale: 3,
});
},
onMouseHoverOut() {
gsap.to(this.cursorBig, 0.3, {
scale: 1,
});
},
},
};
</script>
<style>
#media screen and (min-width: 1100px) {
* {
cursor: none !important;
}
.custom-cursor__ball {
position: fixed;
top: 0;
left: 0;
mix-blend-mode: difference;
z-index: 99999;
opacity: 0;
pointer-events: none;
transition: opacity 0.5s ease;
}
.custom-cursor__ball--big {
content: "";
width: 35px;
height: 35px;
background: black;
border-radius: 50%;
}
.custom-cursor__ball--small {
content: "";
width: 6px;
height: 6px;
background: #000;
border-radius: 50%;
}
}
</style>
i currently facing the same issue, but it solved after adding,
this.$next.tick()
so, i just attached the handler when the components is mounted :
mounted() {
this.$nextTick(() => {
this.transitionController()
window.addEventListener('scroll', () => {
this.transitionController()
})
})
},

Pointer Events API and dragging

I'm trying to implement mobile-only touch UI using Pointer Events API and this just isn't working:
https://codepen.io/kyrsquir/full/gOrZEoe
The goal is to allow scrolling in the card and dragging the card between expanded and collapsed state within the same UI. I only managed to get dragging by the handle to work in Chrome emulator and iOS Safari. Dragging via card content doesn't work anywhere.
On Android Chrome dragging doesn't work at all.
The weirdest part is that the same UI works everywhere using touchstart, touchmove and touchend events (https://codepen.io/kyrsquir/full/QWNZzav) but performance of touchmove in Chrome on Android in the presence of scrollbar is so terrible that I'm trying to reimplement it with pointer events.
Vue component code:
<template>
<div class="container">
<div
:class="containerClass"
:style="containerStyle"
#pointerdown="pointerDownHandler"
#pointermove="pointerMoveHandler"
#pointerup="pointerUpHandler"
#pointerover="reportEvent"
#pointerenter="reportEvent"
#pointercancel="reportEvent"
#pointerout="reportEvent"
#pointerleave="reportEvent"
#gotpointercapture="reportEvent"
#lostpointercapture="reportEvent"
#transitionend="transitionEndHandler"
class="card"
>
<svg viewBox="0 0 100 20" always-swipeable="true" class="drag-handle">
<polyline points="27,10 73,10" stroke-linecap="round"></polyline>
</svg>
<div class="scrollbox" ref="scrollbox">
<div :key="item" class="item" v-for="item in items">
{{ item }}
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
deltaY: 0,
isDraggingByHandle: false,
isTransitioning: false,
items: [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
13,
14,
15,
16,
17,
18,
19,
20,
],
offsetHeight: 0,
scrollboxHeight: 0,
scrollTop: 0,
touchAction: null,
verticalStates: [
{
translateY: 0,
},
{
translateY: window.innerHeight - 200,
},
],
verticalStateIndex: 0,
};
},
computed: {
activeVerticalState() {
return this.verticalStates[this.verticalStateIndex];
},
containerClass() {
return {
"show-scrollbar": this.isScrollable,
transition: this.isTransitioning,
};
},
containerStyle() {
return {
transform: `translateY(${this.translateY}px)`,
};
},
isAnySwipeAllowed() {
return this.isDraggingByHandle || !this.isScrollable;
},
isExpanded() {
return this.verticalStateIndex === 0;
},
isScrollable() {
return this.isExpanded && this.scrollHeight > this.offsetHeight;
},
isSwipeDown() {
return (
this.deltaY > 0 && (this.isAnySwipeAllowed || this.scrollTop === 0)
);
},
isSwipeUp() {
return (
this.deltaY < 0 &&
(this.isAnySwipeAllowed ||
this.offsetHeight + this.scrollTop === this.scrollHeight)
);
},
scrollbox() {
return this.$refs.scrollbox;
},
translateY() {
let translateY = this.activeVerticalState.translateY;
if (
this.touchAction === "verticalSwipe" &&
(this.isSwipeDown || this.isSwipeUp)
) {
translateY = translateY + this.deltaY;
}
return translateY;
},
},
mounted() {
this.updateScrollboxData();
},
methods: {
pointerDownHandler: function ({ clientY, target, pointerId }) {
console.log("pointerdown", target, pointerId);
target.setPointerCapture(pointerId);
this.updateScrollboxData();
this.isDraggingByHandle = Boolean(
target.getAttribute("always-swipeable")
);
this.touchStartClientY = clientY;
this.touchAction = "tap";
},
pointerMoveHandler: function ({ clientY, target, pointerId }) {
console.log("pointermove", target, pointerId, this.deltaY);
this.deltaY = clientY - this.touchStartClientY;
// promote touchAction to swipe or scroll depending on deltas and other variables
if (this.touchAction === "tap") {
if (this.isSwipeDown || this.isSwipeUp) {
this.touchAction = "verticalSwipe";
} else {
this.touchAction = "scroll";
this.updateScrollboxData();
}
}
},
pointerUpHandler: function ({ target, pointerId }) {
console.log("pointerup", target, pointerId);
target.releasePointerCapture(pointerId);
switch (this.touchAction) {
case "tap":
if (!this.isExpanded) {
this.verticalStateIndex = Math.max(this.verticalStateIndex - 1, 0);
this.isTransitioning = true;
}
break;
case "verticalSwipe":
if (this.isSwipeDown) {
this.verticalStateIndex = Math.min(
this.verticalStateIndex + 1,
this.verticalStates.length - 1
);
} else if (this.isSwipeUp) {
this.verticalStateIndex = Math.max(this.verticalStateIndex - 1, 0);
}
this.isTransitioning = true;
this.deltaY = 0;
break;
}
},
reportEvent({ type, target, pointerId }) {
console.log(type, target, pointerId);
},
transitionEndHandler() {
this.touchAction = null;
this.isTransitioning = false;
this.updateScrollboxData();
},
updateScrollboxData() {
const { scrollHeight, offsetHeight, scrollTop } = this.scrollbox;
this.offsetHeight = offsetHeight;
this.scrollHeight = scrollHeight;
this.scrollTop = scrollTop;
},
},
};
</script>
<style lang="scss" scoped>
.container {
display: flex;
justify-content: center;
margin-top: 5vh;
overflow: hidden;
touch-action: none;
.card {
pointer-events: all;
width: 90vw;
height: 100%;
touch-action: none;
&.transition {
transition: transform 0.15s ease-out;
}
.drag-handle {
stroke-width: 5px;
stroke: #bfbfc0;
width: 100%;
height: 30px;
background: pink;
}
.scrollbox {
overflow-y: hidden;
pointer-events: none;
background: green;
height: 85vh;
.item {
margin-bottom: 20px;
height: 150px;
font-size: 36px;
background: yellow;
}
}
&.show-scrollbar .scrollbox {
overflow-y: scroll;
pointer-events: all;
}
}
}
</style>

How to generate Canvas layer with on click button

i have a question - how to draw canvas layer (for example just simple square) with event on click on button in Vue.js? I have stage and on that stage with position x:0, y:0 i want after click on button to generate that square and with drag and drop to position it on that stage? I'm using Konvajs for creating Canvas
Can somebody help me?
<template>
<div id="main">
<h1></h1>
<div id="obszarroboczy" style="width: 500px; height: 600px;">
<v-stage ref="stage"
:config="configKonva"
#dragstart="handleDragstart"
#dragend="handleDragend">
<v-layer ref="layer">
<v-star
v-for="item in list"
:key="item.id"
:config="item"></v-star>
</v-layer>
<v-layer ref="dragLayer"></v-layer>
</v-stage>
</div>
<div class="col-md-6">
<button v-on:click="handleClick" id="more_canvas">More</button>
</div>
</div>
</template>
<script>
import Vue from "vue";
import axios from "axios";
import draggable from "vuedraggable";
import swal from "sweetalert2";
import VueKonva from "vue-konva";
export default {
name: "EnumCurrencyIndex",
$mount: "#main",
components: {
draggable
},
data() {
return {
model: [],
editable: true,
isDragging: false,
delayedDragging: false,
type: "currency",
editedElement: null,
newElement: "",
list: [],
configKonva: {
width: 400,
height: 400
},
configCircle: {
x: 100,
y: 100,
radius: 70,
fill: 'red',
stroke: 'black',
strokeWidth: 4
},
vm: {}
};
},
beforeMount() {
this.fetchData();
},
computed: {
dragOptions() {
return {
animation: 0,
group: "description",
disabled: !this.editable,
ghostClass: "ghost"
};
},
listString() {
return this.model;
},
dragCanvas() {
return this.model;
}
},
watch: {
$route: "fetchData",
isDragging(newValue) {
if (newValue) {
this.delayedDragging = true;
return;
}
this.$nextTick(() => {
this.delayedDragging = false;
});
}
},
methods: {
handleDragstart(starComponent) {
var vm = this;
const shape = starComponent.getStage();
const dragLayer = vm.$refs.dragLayer.getStage();
const stage = vm.$refs.stage.getStage();
// moving to another layer will improve dragging performance
shape.moveTo(dragLayer);
stage.draw();
starComponent.config.shadowOffsetX = 15;
starComponent.config.shadowOffsetY = 15;
starComponent.config.scaleX = starComponent.config.startScale * 1.2;
starComponent.config.scaleY = starComponent.config.startScale * 1.2;
},
handleDragend(starComponent) {
var vm = this;
const shape = starComponent.getStage();
const layer = vm.$refs.layer.getStage();
const stage = vm.$refs.stage.getStage();
shape.moveTo(layer);
stage.draw();
shape.to({
duration: 0.5,
easing: Konva.Easings.ElasticEaseOut,
scaleX: starComponent.config.startScale,
scaleY: starComponent.config.startScale,
shadowOffsetX: 5,
shadowOffsetY: 5
});
},
handleClick(configCircle) {
var vm = this;
const shape = vm.$refs.layer.getStage();
const layer = vm.$refs.layer.getStage();
const stage = vm.$refs.stage.getStage();
console.log(1);
layer.add(configCircle);
stage.add(layer);
},
haveIntersection(r1, r2) {
return !(
r2.x > r1.x + r1.width ||
r2.x + r2.width < r1.x ||
r2.y > r1.y + r1.height ||
r2.y + r2.height < r1.y
);
},
orderList() {
this.model = this.model.sort((one, two) => {
return one.position - two.position;
});
},
onMove({ relatedContext, draggedContext }) {
const relatedElement = relatedContext.element;
const draggedElement = draggedContext.element;
return (
(!relatedElement || !relatedElement.fixed) && !draggedElement.fixed
);
},
fetchData() {
var vm = this;
axios
.get(`/api/${this.resource}?type=${this.type}`)
.then(function(response) {
Vue.set(vm.$data, "model", response.data.model);
})
.catch(function(error) {
console.log(error);
});
}
},
mounted() {
var box = document.getElementById("obszarroboczy");
this.configKonva.width = box.offsetWidth;
this.configKonva.height = box.offsetHeight;
var vm = this;
for (let n = 0; n < 30; n++) {
const scale = Math.random();
const stage = vm.$refs.stage.getStage();
vm.list.push({
x: Math.random() * stage.getWidth(),
y: Math.random() * stage.getHeight(),
rotation: Math.random() * 180,
numPoints: 5,
innerRadius: 30,
outerRadius: 50,
fill: "#89b717",
opacity: 0.8,
draggable: true,
scaleX: scale,
scaleY: scale,
shadowColor: "black",
shadowBlur: 10,
shadowOffsetX: 5,
shadowOffsetY: 5,
shadowOpacity: 0.6,
startScale: scale
});
};
},
directives: {
"element-focus": function(el, binding) {
if (binding.value) {
el.focus();
}
}
}
};
</script>
<style>
#obszarroboczy {
width: 100px;
height: 300px;
}
.normal {
background-color: grey;
}
.table td {
width: 100px;
height: 100px;
background: white;
border: 2px dotted black;
max-width: 100px;
padding: 5px;
}
.drag {
display: flex;
flex-direction: row;
}
.list {
flex-grow: 1;
max-width: 47%;
margin-right: 40px;
}
.name {
width: 50%;
display: inline-block;
height: 50px;
background: pink;
border: 5px green solid;
box-sizing: border-box;
padding: 5px;
}
.name.large {
width: 100%;
}
.dragArea {
min-height: 100px;
}
.dragArea img {
margin: 3px;
cursor: pointer;
}
</style>
var mainCanvas = new Vue({
el: '#main', // the element where the method wil lrender the canvas to
data: {
name: 'Vue.js'
},
methods: {
handleClick: function (event) { // handleClick is the method name for the button
var stage = new Konva.Stage({ // this line till the stage.add() line renders the draggable square
container: 'obszarroboczy',
width: 500,
height: 500
});
var layer = new Konva.Layer();
var rect = new Konva.Rect({
x: 0,
y: 0,
width: 100,
height: 100,
fill: 'green',
stroke: 'black',
strokeWidth: 4,
draggable: true
});
layer.add(rect);
stage.add(layer);
}
}
});
I added comments to explain what certain important lines does but you can check out the official KonvaJS Docs in GitHub for a more detailed explanation on what each line above does.

QtQuick2 - custom MessageBox

Does anyone know how to implement custom MessageBox for mobile devices? I've tried to use Window type, but with no luck (it just shows but somewhere out of screen). I appreciate if someone can show me why usage of Window doesn't work. I used also this example. But on mobile devices it doesn't work.
Here is my current code, using Window. As said, it doesn't work since it does show out of screen range.
import QtQuick 2.4
import QtQuick.Window 2.1
Item{
function showMessage(text, title)
{
messageBox.text = text;
messageBox.title = title;
messageBox.visible = true;
}
Window {
id: messageBox
modality: Qt.ApplicationModal
title: ""
visible: false
property alias text: messageBoxLabel.text
color: parent.color
minimumHeight: 100
minimumWidth: 300
Label {
anchors.margins: 10
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: messageBoxButton.top
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
id: messageBoxLabel
text: ""
}
Button {
anchors.margins: 10
id: messageBoxButton
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: "Ok"
onClicked: messageBox.visible = false
}
}
}
Can someone show me why it's working wrong?
MessageBox.qml
import QtQuick 2.2
import QtQuick.Controls 1.2
Rectangle {
id: mainWrapper
color: "#80000000"
x: 0;
y: 0;
width: parent.width;
height: parent.height;
opacity: 0;
Behavior on opacity { NumberAnimation { duration: 500; easing.type: Easing.OutExpo } }
visible: opacity > 0
property string text;
MouseArea {
anchors.fill: parent;
preventStealing: true
}
signal finished(bool ok);
function init() {
opacity = 1;
msgB.scale = 1.0;
}
Rectangle {
id: msgB
color: "#323232"
gradient: Gradient {
GradientStop { position: 0; color: "#323232" }
GradientStop { position: 1; color: "#252525" }
}
//radius: 7
width: parent.width * 0.4;
height: cont.height + 20 * 2;
anchors.centerIn: parent;
scale: 0.6
Behavior on scale { NumberAnimation { duration: 500; easing.type: Easing.OutExpo } }
Behavior on height { NumberAnimation { duration: 500; easing.type: Easing.OutExpo } }
Column {
id: cont
width: parent.width;
y: 20;
spacing: 20;
Text {
color: "#ffffff"
horizontalAlignment: Text.AlignHCenter
anchors.horizontalCenter: parent.horizontalCenter
font {
bold: false;
pixelSize: 21;
}
wrapMode: Text.WordWrap;
text: mainWrapper.text;
}
Button {
anchors.margins: 10
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text: "OK"
onClicked: {
mainWrapper.opacity = 0;
msgB.scale = 0.6;
mainWrapper.finished(true);
}
}
}
}
}
Somewhere in main.qml file (window is the id of main.qml element):
function message(msg, finished) {
var alert = Qt.createComponent("MessageBox.qml").createObject(window, { text: msg });
alert.onFinished.connect(function(ok) {
if (ok) {
if (finished)
finished();
}
alert.destroy(500);
});
alert.init();
return alert;
}
Use it like this:
Button {
...
onClicked: {
message("Hello world", function() { console.log("OK clicked"); });
}
}
Thanks all for answers and comments, summarizing above I created element without Window type, but with contentItem property. It's very raw element, but usable like Dialog as suggested by BaCaRoZzo or Window as in Mechan example.
Here is source:
main.qml
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Window 2.2
ApplicationWindow {
title: qsTr("Hello World")
width: Screen.width
height: Screen.height
visible: true
id: win
color: brPalette.charcoal
BreezeQuickMessageBox{
id: mbox
palette: brPalette
contentItem: Rectangle{
color: "lightblue"
anchors.fill: parent
BreezeQuickButton{
id: btn
anchors {
verticalCenter: parent.verticalCenter
horizontalCenter: parent.horizontalCenter
}
palette: brPalette
gradiented: false
onClicked: {
mbox.hide()
}
}
}
}
/*
Another bunch of code
*/
BreezeQuickPalette{
id: brPalette
theme: "dark"
}
}
BreezeQuickMessageBox.qml
import QtQuick 2.4
Item {
id: root
property BreezeQuickPalette palette: BreezeQuickPalette
property bool __buttonGradiented: false
property string title: "Message Box"
property Item contentItem
anchors.fill: parent
Behavior on opacity {
NumberAnimation{
duration: 250
}
}
opacity: 0
visible: opacity > 0
z: parent.z + 100
BreezeQuickPalette{
id: __palette
theme: palette.theme
}
Rectangle {
id: window
width: parent.width
height: parent.height*0.4
anchors {
verticalCenter: parent.verticalCenter
}
z: parent.z + 1
color: palette.charcoal
Item {
id: content
width: parent.width
anchors {
top: titleText.bottom
bottom: line.top
horizontalCenter: parent.horizontalCenter
topMargin: 8
bottomMargin: 8
}
children: contentItem
}
Rectangle{
id: line
width: parent.width
anchors{
bottom: buttonArea.top
horizontalCenter: parent.horizontalCenter
}
height: 1
color: palette.focusColor
}
Text{
id: titleText
font.pointSize: buttonOk.font.pointSize
color: palette.normalText
text: title
anchors {
top: parent.top
horizontalCenter: parent.horizontalCenter
topMargin: 16
}
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
Rectangle{
id: buttonArea
width: parent.width
anchors{
horizontalCenter: parent.horizontalCenter
bottom: window.bottom
}
height: buttonOk.height*1.2
color: "transparent"
}
BreezeQuickButton {
id: buttonOk
caption: "Ok"
width: 128
palette: __palette
gradiented: __buttonGradiented
anchors{
horizontalCenter: parent.horizontalCenter
verticalCenter: buttonArea.verticalCenter
}
onClicked: {
root.hide()
}
}
}
Rectangle{
id: shadow
anchors.fill: parent
z: parent.z
color: palette.shadeBlack
opacity: 0.4
MouseArea{
id: rootArea
anchors.fill: parent
hoverEnabled: true
}
gradient: Gradient {
GradientStop { position: 0.0; color: palette.black }
GradientStop { position: 0.1; color: palette.shadeBlack }
GradientStop { position: 0.3; color: palette.grey }
GradientStop { position: 0.7; color: palette.grey }
GradientStop { position: 0.9; color: palette.shadeBlack }
GradientStop { position: 1.0; color: palette.black }
}
}
function show (title, message) {
root.opacity = 1
}
function hide () {
root.opacity = 0
}
}
And actual look for Android:

Categories

Resources