How to generate Canvas layer with on click button - javascript

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.

Related

Integrating CytoscapeJs with LeafletJs

I have made a graph component in CytoscapeJS and want to have a map as an overlay. Basically I want to represent the nodes based on their co-ordinates on Map (using LeafletJs).
I have looked into the plugin (cytoscape-mapbox-gl by zakjan) and cytoscape-leaf extension (using this currently).
I am getting this error:
Uncaught TypeError: obj.attachEvent is not a function
Uncaught TypeError: Cannot read properties of undefined (reading 'lat')
Uncaught Error: Map container is already initialized.
I only want to integrate CytoscapeJs with LeafletJS. I don't want to add any tile layer.
Followed all the steps on the Cytoscape-leaf plugin documentation.
I have registered the function, created the instance, added the co-ordinates in the data object,rendered the Map component alongside cytoscape component, Added the lat,lng field in the data object for cytoscape node(Node Position).
cytoscape.use( leaflet );
const map = useRef();
data: {
id: ele.nodeAddr,
label: `IP-${ele.nodeAddr}`,
icon:Server,
type:'parentNode',
status:ele.status,
lat:19.5,
lng:72.8777,
}
const options = {
container: map.current,
// the data field for latitude
latitude: 'lat',
// the data field for longitude
longitude: 'lng'
};
const leaf = cy.leaflet(options);
<div
ref={divRef}
style={{
border: "1px solid",
backgroundColor: "#f5f6fe",
height: '600px',
}}
>
<div ref={map}></div>
My answer is independent of your code and your intended plugins you want to use with cytoscape. It may not answer your question, but it works to some extent that I consider it a good start to use cytoscapeJS within leaflet.
One issue that I found, it wont work with browsers on iOS or ipadOS. I am sorry about that. Help is welcome to solve the issues why Webkit based browsers on iOS/ipadOS do not handle cytoscape's graph like Chrome browsers on android/windows.
Code:
const base_dc = 0.027589;
const lon_ext = 0.5436;
const lat_ext = 0.5436;
const lonmin = 100.3257;
const latmin = 13.4978;
const lon_cor = lon_ext * base_dc;
const lonmax = lonmin + lon_ext;
const lonmid = (lonmin + lonmax) / 2;
const lonmax2 = lonmax + lon_cor;
const latmax = latmin + lat_ext;
const latmid = (latmin + latmax) / 2;
const latlng = { lat: latmin, lng: lonmin };
const latlng2 = { lat: latmax, lng: lonmax2 };
const clatlng = { lat: latmid, lng: lonmid };
const cbottom = { lat: latmin, lng: lonmid };
var zoom = 9;
const myRenderer = L.canvas({ padding: 0.0 }); //svg or canvas
/* Cytoscape global vars */
// must match SVG viewbox w h.
// and width, height of #cyOnSvg DIV
const cy_size = 1600;
const params = {
width: cy_size,
height: cy_size,
latmin: latmin,
latmax: latmax,
lonmin: lonmin,
lonmax: lonmax2
};
// Update CSS to use new cy_size
let root = document.documentElement;
root.style.setProperty("--svg-width", cy_size + "px");
root.style.setProperty("--svg-height", cy_size + "px");
/* choice of maps */
var mymap = L.map("mapid", {
renderer: myRenderer,
preferCanvas: false
}).setView(clatlng, zoom);
L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
attribution: "Map data © OpenStreetMap contributors"
}).addTo(mymap);
const svg_pin =
'<svg width="24" height="24" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M12,11.5A2.5,2.5 0 0,1 9.5,9A2.5,2.5 0 0,1 12,6.5A2.5,2.5 0 0,1 14.5,9A2.5,2.5 0 0,1 12,11.5M12,2A7,7 0 0,0 5,9C5,14.25 12,22 12,22C12,22 19,14.25 19,9A7,7 0 0,0 12,2Z" fill="firebrick"></path></svg>';
const svgpin_Url = encodeURI("data:image/svg+xml;utf-8," + svg_pin);
const svgpin_Icon = L.icon({
iconUrl: svgpin_Url,
iconSize: [24, 24],
iconAnchor: [12, 24],
popupAnchor: [0, -22]
});
var marker2 = L.marker(latlng2, {
renderer: myRenderer,
icon: svgpin_Icon
//draggable: true,
//autoPan: true
}).addTo(mymap);
marker2.bindPopup("<b>Control_UR</b>").openPopup();
marker2.openPopup();
var marker0 = L.marker(latlng, {
renderer: myRenderer,
icon: svgpin_Icon
}).addTo(mymap);
marker0.bindPopup("<b>Control_LL</b>").openPopup();
marker0.openPopup();
/* Embed SVG Element in the web page */
let svgElem = document.createElementNS("http://www.w3.org/2000/svg", "svg");
svgElem.setAttribute("xmlns", "http://www.w3.org/2000/svg");
svgElem.setAttribute("id", "Svg0");
svgElem.setAttribute("preserveAspectRatio", "xMinYMax slice");
svgElem.setAttribute("viewBox", `0 0 ${cy_size} ${cy_size}`);
/* SVGOverlay with foreignObject-div-text */
svgElem.innerHTML = `<foreignObject x="0" y="0" width="100%" height="100%">
<div id="cyOnSvg" style="background-color:rgba(200,200,200,0.25);padding:0;"></div>
<div id="upperlefttext">Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</div>
</foreignObject>`;
// Note: lonmax2 (=lonmax + correction)
const svgElementBounds = [
[latmin, lonmin],
[latmax, lonmax2]
];
const svgobj = L.svgOverlay(svgElem, svgElementBounds, {
renderer: myRenderer,
zIndex: 15,
opacity: 0.65,
interactive: false
}).addTo(mymap);
//svgobj.bindPopup("SVG vector layer");
//var draggable = new L.Draggable(svgElem);
//draggable.enable(); //make draggable (must set=> interactive: true)
/* SVG OK */
/* set events */
mymap.on("click", onMapClick);
//svgobj.on("click", onSvgClick);
/* other useful settings */
mymap.scrollWheelZoom.disable();
const popup = L.popup(); //leaflet popup object
function onMapClick(e) {
popup.setLatLng(e.latlng).setContent(e.latlng.toString()).openOn(mymap);
}
/* -------cytoscape-------- */
function lnglat2xy(lon, lat, pars) {
//let w = pars.width;
//let h = pars.height;
let L = pars.lonmax - pars.lonmin; //lonmax2-lonmin
let B = pars.latmax - pars.latmin;
let y = ((B - (lat - pars.latmin)) * pars.height) / B;
let x = ((lon - pars.lonmin) * pars.width) / L;
return { x: x, y: y };
}
const lngMidLatMid = lnglat2xy(
(lonmin + lonmax2) / 2,
(latmin + latmax) / 2,
params
);
//console.log("lngMidLatMid: "+lngMidLatMid.x+"; "+lngMidLatMid.y);
const bkk = lnglat2xy(100.5348327, 13.7567441, params);
const bda = lnglat2xy(100.4094999, 13.7108552, params);
const hoc = lnglat2xy(100.6076988, 13.5676449, params);
const han = lnglat2xy(100.7522551, 13.6982529, params);
const nay = lnglat2xy(100.4081876, 13.8923587, params);
var cy = cytoscape({
container: document.querySelector("#cyOnSvg"),
elements: {
nodes: [
{
data: { id: "LL", name: "LowerLeft" },
classes: "controlpoint",
position: { x: 0, y: cy_size }
},
{
data: { id: "UL", name: "UpperLeft" },
classes: "controlpoint",
position: { x: 0, y: 0 }
},
{
data: { id: "UR", name: "UpperRight" },
classes: "controlpoint",
position: { x: cy_size, y: 0 }
},
{
data: { id: "LR", name: "LowerRight" },
classes: "controlpoint",
position: { x: cy_size, y: cy_size }
},
/* Nodes with (long,lat) coordinates */
{ data: { id: "bkk", name: "A8" }, position: { x: bkk.x, y: bkk.y } },
{ data: { id: "bda", name: "BL38" }, position: { x: bda.x, y: bda.y } },
{ data: { id: "hoc", name: "E23" }, position: { x: hoc.x, y: hoc.y } },
{ data: { id: "han", name: "A1" }, position: { x: han.x, y: han.y } },
{ data: { id: "nay", name: "PP01" }, position: { x: nay.x, y: nay.y } },
],
edges: [
{
data: { id: "LLUR", source: "LL", target: "UR" },
classes: "controlline"
},
{
data: { id: "ULLR", source: "UL", target: "LR" },
classes: "controlline"
},
{
data: { id: "bkk_bda", source: "bkk", target: "bda" },
classes: "edge"
},
{
data: { id: "bkk_han", source: "bkk", target: "han" },
classes: "edge"
},
{
data: { id: "bkk_hoc", source: "bkk", target: "hoc" },
classes: "edge"
},
{
data: { id: "bkk_nay", source: "bkk", target: "nay" },
classes: "edge"
},
]
},
style: [
{
selector: "node",
style: {
shape: "hexagon",
width: "50px",
height: "50px",
"background-color": "blue",
label: "data(name)",
opacity: 1,
"text-background-color": "yellow"
}
},
{
selector: ".controlline",
style: {
width: "2px",
"line-color": "black",
opacity: 1
}
},
{
selector: ".edge",
style: {
width: "3px",
"line-color": "red",
opacity: 1
}
},
],
layout: {
name: "preset"
}
});
cy.pan({ x: 0.0, y: 0.0 }); //match TOP-LEFT corner
cy.fit(cy.$("#LLUR"));
:root {
--svg-width: 1600px;
--svg-height: 1600px;
}
#cyOnSvg{
/* foreignObject, and canvas */
position: absolute;
width: 100%; //5%;
height: 100%; //10%;
top: 0;
left: 0;
background-color: lightgray;
overflow: visible;
margin: 0;
padding: 0;
}
#Svg0{
/* svg elem */
position: absolute;
width: var(--svg-width);
height: var(--svg-height);
top: 0;
left: 0;
display: block;
background-color: lightyellow;
/*border: 0.5px solid gray;
border-style: dashed;*/
overflow: visible;
margin: 0;
padding: 0;
}
#mapid {
height: 480px;
width: 480px;
}
#lorem {
width: var(--svg-width);
position: absolute;
background-color: gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.9.2/cytoscape.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-browser/0.1.0/jquery.browser.min.js"></script>
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.5.1/dist/leaflet.css" integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ==" crossorigin="" />
<div id="mapid"></div>

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>

Start next line from somewhere else

Now i have choice of different colors, but when selecting other color, line is still drawn from last point of latest line. How could i draw in new fresh place after choosing other color?
function drawLine() {
canvas.on("mouse:down", function(event) {
var pointer = canvas.getPointer(event.e);
var positionX = pointer.x;
var positionY = pointer.y;
var circlePoint = new fabric.Circle({
radius: 1,
fill: pipeColor,
left: positionX,
top: positionY,
selectable: false,
originX: "center",
originY: "center",
hoverCursor: "auto"
});
canvas.add(circlePoint);
// Store the points to draw the lines
pipePoints.push(circlePoint);
// console.log(pipePoints);
if (pipePoints.length > 1) {
var startPoint = pipePoints[pipePoints.length - 2];
var endPoint = pipePoints[pipePoints.length - 1];
var pipeLine = new fabric.Line(
[
startPoint.get("left"),
startPoint.get("top"),
endPoint.get("left"),
endPoint.get("top")
],
{
stroke: pipeColor,
strokeWidth: 2,
hasControls: false,
hasBorders: false,
selectable: false,
lockMovementX: true,
lockMovementY: true,
hoverCursor: "default",
originX: "center",
originY: "center"
}
);
pipeLines.push(pipeLine);
canvas.add(pipeLine);
}
});
$('#colorpicker').change(function () {
canvas.item(0).selectable = false;
pipeColor = $(this).val();
drawLine();
});
}
For now I can draw any lines i want starting at point X and finishing at point Y, but when i change color of line i wanna start at point Z not point Y
It is not much FabricJS related question. It is more about the implementation and the needs of the app. It can vary cause of requirements and the programmer's experience. Anyway, here is an example width a demo how you can potentially draw multiple lines and go back to the previous lines and continue editing them. You can check the inline StackOverflow snipper or check it on CodeSandbox:
https://codesandbox.io/s/stackoverflow-60753858-fabric-js-1720-6ke80
var canvas = new fabric.Canvas("canvas");
var currentLine = "water";
document
.getElementsByClassName("options")[0]
.addEventListener("click", function(e) {
if (e.target.classList.contains("line-type")) {
// Remove active class from previous element
document
.querySelector(".line-type.is-active")
.classList.remove("is-active");
e.target.classList.add("is-active");
currentLine = e.target.dataset.lineType;
console.warn("Current line: " + currentLine);
}
});
var linesData = {
water: {
linePoints: [],
lineLines: [],
color: "blue"
},
electricity: {
linePoints: [],
lineLines: [],
color: "yellow"
},
internet: {
linePoints: [],
lineLines: [],
color: "gray"
}
};
canvas.on("mouse:down", function(event) {
var pointer = canvas.getPointer(event.e);
var positionX = pointer.x;
var positionY = pointer.y;
// Add small circle as an indicative point
var circlePoint = new fabric.Circle({
radius: 5,
fill: linesData[currentLine].color,
left: positionX,
top: positionY,
selectable: false,
originX: "center",
originY: "center",
hoverCursor: "auto"
});
canvas.add(circlePoint);
// Store the points to draw the lines
linesData[currentLine].linePoints.push(circlePoint);
if (linesData[currentLine].linePoints.length > 1) {
// Just draw a line using the last two points, so we don't need to clear
// and re-render all the lines
var startPoint =
linesData[currentLine].linePoints[
linesData[currentLine].linePoints.length - 2
];
var endPoint =
linesData[currentLine].linePoints[
linesData[currentLine].linePoints.length - 1
];
var waterLine = new fabric.Line(
[
startPoint.get("left"),
startPoint.get("top"),
endPoint.get("left"),
endPoint.get("top")
],
{
stroke: linesData[currentLine].color,
strokeWidth: 4,
hasControls: false,
hasBorders: false,
selectable: false,
lockMovementX: true,
lockMovementY: true,
hoverCursor: "default",
originX: "center",
originY: "center"
}
);
linesData[currentLine].lineLines.push(waterLine);
canvas.add(waterLine);
}
});
canvas.renderAll();
document
.getElementById("clear-water-pipe")
.addEventListener("click", function(e) {
linesData[currentLine].linePoints.forEach(function(point) {
canvas.remove(point);
});
linesData[currentLine].linePoints = [];
linesData[currentLine].lineLines.forEach(function(line) {
canvas.remove(line);
});
linesData[currentLine].lineLines = [];
});
body {
font-family: sans-serif;
}
canvas {
border: 2px solid #333;
}
button {
margin-top: 10px;
padding: 10px;
border: 1px solid #555;
cursor: pointer;
}
.options {
padding-top: 10px;
}
.lines {
display: flex;
align-items: center;
}
.lines > div {
border: 2px solid #333;
color: white;
margin-right: 10px;
padding: 10px;
}
.lines > div.is-active {
border: 5px solid red;
}
.water-pipe {
background-color: blue;
}
.lines .electricity-line {
background-color: yellow;
color: #555;
}
.internet-line {
background-color: gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.7.22/fabric.min.js"></script>
<div id="app">
<canvas id="canvas" width="500" height="350"></canvas>
<div class="options">
<div class="lines" id="lines">
<div class="line-type water-pipe is-active" data-line-type="water">
Water pipe
</div>
<div class="line-type electricity-line" data-line-type="electricity">
Electricity line
</div>
<div class="line-type internet-line" data-line-type="internet">
Internet line
</div>
</div>
<button id="clear-water-pipe">Clear active line</button>
</div>
</div>

Mix-Blend mode doesn't work on custom cursor

I would like to add a mix-blend-mode: difference to the inner cursor, but I can't get it to work. I heard that the position: fixed applied to the cursor may cause the problem here. But I can't remove that property, because the cursor needs to follow when scrolling down. Does anyone know how to fix the problem here? Thank you in advance.
class Demo {
constructor() {
this.initCursor();
this.initHovers();
}
initCursor() {
const { Back } = window;
this.outerCursor = document.querySelector(".circle-cursor-outer");
this.innerCursor = document.querySelector(".circle-cursor-inner");
this.outerCursorBox = this.outerCursor.getBoundingClientRect();
this.outerCursorSpeed = 0;
this.easing = Back.easeOut.config(1.7);
this.clientX = -100;
this.clientY = -100;
this.showCursor = false;
const unveilCursor = () => {
TweenMax.set(this.innerCursor, {
x: this.clientX,
y: this.clientY
});
TweenMax.set(this.outerCursor, {
x: this.clientX - this.outerCursorBox.width / 2,
y: this.clientY - this.outerCursorBox.height / 2
});
setTimeout(() => {
this.outerCursorSpeed = 0.2;
}, 100);
this.showCursor = true;
};
document.addEventListener("mousemove", unveilCursor);
document.addEventListener("mousemove", e => {
this.clientX = e.clientX;
this.clientY = e.clientY;
});
const render = () => {
TweenMax.set(this.innerCursor, {
rotation: 90,
x: this.clientX,
y: this.clientY
});
if (!this.isStuck) {
TweenMax.to(this.outerCursor, this.outerCursorSpeed, {
x: this.clientX - this.outerCursorBox.width / 2,
y: this.clientY - this.outerCursorBox.height / 2
});
}
if (this.showCursor) {
document.removeEventListener("mousemove", unveilCursor);
}
requestAnimationFrame(render);
};
requestAnimationFrame(render);
}
initHovers() {
const handleMouseEnter = e => {
this.isStuck = true;
const target = e.currentTarget;
const box = target.getBoundingClientRect();
this.outerCursorOriginals = {
width: this.outerCursorBox.width,
height: this.outerCursorBox.height
};
TweenMax.to(this.innerCursor, 0.2, {
x: box.left,
y: box.top,
width: box.width,
height: box.height,
opacity: 0.4,
});
};
const handleMouseLeave = () => {
this.isStuck = false;
TweenMax.to(this.innerCursor, 0.2, {
width: this.outerCursorOriginals.width,
height: this.outerCursorOriginals.height,
opacity: 0.2,
});
};
const mainNavHoverTween = TweenMax.to(this.innerCursor, 0.3, {
ease: this.easing,
paused: true,
opacity: 1,
width: 70,
height: 70,
left: -35,
top: -35
});
const mainNavMouseEnter = () => {
this.outerCursorSpeed = 0;
TweenMax.set(this.innerCursor, { opacity: 1 });
mainNavHoverTween.play();
};
const mainNavMouseLeave = () => {
this.outerCursorSpeed = 0.2;
TweenMax.set(this.innerCursor, { opacity: 1 });
mainNavHoverTween.reverse();
};
const mainNavLinks = document.querySelectorAll("a");
mainNavLinks.forEach(item => {
item.addEventListener("mouseenter", mainNavMouseEnter);
item.addEventListener("mouseleave", mainNavMouseLeave);
});
}
}
const demo = new Demo();
body{
height: 500vh;
}
a{
color: black;
}
.circle-cursor {
position: fixed;
left: 0;
top: 0;
pointer-events: none;
border-radius: 100%;
}
.circle-cursor-outer-badge {
opacity: 0.5;
transition: all 500ms ease-out;
}
.circle-cursor-inner {
width: 22.5px;
height: 22.5px;
left: -11.25px;
top: -11.25px;
z-index: 11000;
mix-blend-mode: difference;
background-color: black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
I am a link
<div class="circle-cursor circle-cursor-outer">
<img class="circle-cursor-outer-badge" draggable="false" width="120" src="https://svgshare.com/i/H2V.svg">
</div>
<div class="circle-cursor circle-cursor-inner"></div>
</body>
Just set background:white to both body and .circle-cursor-inner:
class Demo {
constructor() {
this.initCursor();
this.initHovers();
}
initCursor() {
const { Back } = window;
this.outerCursor = document.querySelector(".circle-cursor-outer");
this.innerCursor = document.querySelector(".circle-cursor-inner");
this.outerCursorBox = this.outerCursor.getBoundingClientRect();
this.outerCursorSpeed = 0;
this.easing = Back.easeOut.config(1.7);
this.clientX = -100;
this.clientY = -100;
this.showCursor = false;
const unveilCursor = () => {
TweenMax.set(this.innerCursor, {
x: this.clientX,
y: this.clientY
});
TweenMax.set(this.outerCursor, {
x: this.clientX - this.outerCursorBox.width / 2,
y: this.clientY - this.outerCursorBox.height / 2
});
setTimeout(() => {
this.outerCursorSpeed = 0.2;
}, 100);
this.showCursor = true;
};
document.addEventListener("mousemove", unveilCursor);
document.addEventListener("mousemove", e => {
this.clientX = e.clientX;
this.clientY = e.clientY;
});
const render = () => {
TweenMax.set(this.innerCursor, {
rotation: 90,
x: this.clientX,
y: this.clientY
});
if (!this.isStuck) {
TweenMax.to(this.outerCursor, this.outerCursorSpeed, {
x: this.clientX - this.outerCursorBox.width / 2,
y: this.clientY - this.outerCursorBox.height / 2
});
}
if (this.showCursor) {
document.removeEventListener("mousemove", unveilCursor);
}
requestAnimationFrame(render);
};
requestAnimationFrame(render);
}
initHovers() {
const handleMouseEnter = e => {
this.isStuck = true;
const target = e.currentTarget;
const box = target.getBoundingClientRect();
this.outerCursorOriginals = {
width: this.outerCursorBox.width,
height: this.outerCursorBox.height
};
TweenMax.to(this.innerCursor, 0.2, {
x: box.left,
y: box.top,
width: box.width,
height: box.height,
opacity: 0.4,
});
};
const handleMouseLeave = () => {
this.isStuck = false;
TweenMax.to(this.innerCursor, 0.2, {
width: this.outerCursorOriginals.width,
height: this.outerCursorOriginals.height,
opacity: 0.2,
});
};
const mainNavHoverTween = TweenMax.to(this.innerCursor, 0.3, {
ease: this.easing,
paused: true,
opacity: 1,
width: 70,
height: 70,
left: -35,
top: -35
});
const mainNavMouseEnter = () => {
this.outerCursorSpeed = 0;
TweenMax.set(this.innerCursor, { opacity: 1 });
mainNavHoverTween.play();
};
const mainNavMouseLeave = () => {
this.outerCursorSpeed = 0.2;
TweenMax.set(this.innerCursor, { opacity: 1 });
mainNavHoverTween.reverse();
};
const mainNavLinks = document.querySelectorAll("a");
mainNavLinks.forEach(item => {
item.addEventListener("mouseenter", mainNavMouseEnter);
item.addEventListener("mouseleave", mainNavMouseLeave);
});
}
}
const demo = new Demo();
body{
height: 500vh;
background:white;
}
a{
color: black;
}
.circle-cursor {
position: fixed;
left: 0;
top: 0;
pointer-events: none;
border-radius: 100%;
}
.circle-cursor-outer-badge {
opacity: 0.5;
transition: all 500ms ease-out;
}
.circle-cursor-inner {
width: 22.5px;
height: 22.5px;
left: -11.25px;
top: -11.25px;
z-index: 11000;
mix-blend-mode: difference;
background: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
I am a link
<div class="circle-cursor circle-cursor-outer">
<img class="circle-cursor-outer-badge" draggable="false" width="120" src="https://svgshare.com/i/H2V.svg">
</div>
<div class="circle-cursor circle-cursor-inner"></div>
</body>

Categories

Resources