draw rectangle with mouse move with Konva in VUE - vue.js

This is the behavior I want to achieve in Vue.js Here is the Js fiddle example i am trying to make: https://jsfiddle.net/richardcwc/ukqhf54k/
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
//Mousedown
$(canvas).on('mousedown', function(e) {
last_mousex = parseInt(e.clientX-canvasx);
last_mousey = parseInt(e.clientY-canvasy);
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
mousex = parseInt(e.clientX-canvasx);
mousey = parseInt(e.clientY-canvasy);
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
ctx.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
ctx.rect(last_mousex,last_mousey,width,height);
ctx.strokeStyle = 'black';
ctx.lineWidth = 10;
ctx.stroke();
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
I am using a library called Konva.js. Right now I am able to free drawing in Vue.js with Konva.js. But When I try to draw the rectangle with mousemove. It does not work correctly. I am not sure what causes the issue. Thanks for any help! Here is my work on
Code sandbox
This is the behavior I found out for my work. It only draws the rectangle after the mouse move event and then mouse click event.

<template>
<v-stage
ref="stage"
:config="stageSize"
#mousemove="handleMouseMove"
#mouseDown="handleMouseDown"
#mouseUp="handleMouseUp"
>
<v-layer ref="layer">
<v-text
ref="text"
:config="{
x: 10,
y: 10,
fontSize: 20,
text: text,
fill: 'black',
}"
/>
<v-rect
v-for="(rec, index) in recs"
: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>
</template>
<script>
const width = window.innerWidth;
const height = window.innerHeight;
export default {
data() {
return {
stageSize: {
width: width,
height: height,
},
text: "Try to draw a rectangle",
lines: [],
isDrawing: false,
recs: [],
};
},
methods: {
handleMouseDown(event) {
this.isDrawing = true;
const pos = this.$refs.stage.getNode().getPointerPosition();
this.setRecs([
...this.recs,
{ startPointX: pos.x, startPointY: pos.y, width: 0, height: 0 },
]);
},
handleMouseUp() {
this.isDrawing = false;
},
setRecs(element) {
this.recs = element;
},
handleMouseMove(event) {
// no drawing - skipping
if (!this.isDrawing) {
return;
}
// console.log(event);
const point = this.$refs.stage.getNode().getPointerPosition();
// handle rectangle part
let curRec = this.recs[this.recs.length - 1];
curRec.width = point.x - curRec.startPointX;
curRec.height = point.y - curRec.startPointY;
},
},
};
</script>
Demo: https://codesandbox.io/s/vue-konva-drawings-rectangles-ivjtu?file=/src/App.vue

Related

HTML5 canvas artifacts

I have created a Vue component that loads an image into a canvas and allows the user to zoom-in and zoom-out with the mouse wheel. The image resolution is 1260x1800, the canvas resolution is 427x610 (the aspect ratio is preserved).
<template>
<canvas
ref="canvas"
:width="width"
:height="height"
#mousedown.prevent.stop="onMouseDown"
#mousemove.prevent.stop="onMouseMove"
#mouseup.prevent.stop="onMouseUp"
#mouseout.prevent.stop="onMouseOut"
#mousewheel.prevent.stop="onMouseWheel"
/>
</template>
<script>
export default {
name: 'MyZoomingCanvas',
props: {
src: {
type: String,
required: true
},
zoom: {
type: Object,
required: true,
validator: zoom =>
zoom &&
zoom.hasOwnProperty('min') &&
zoom.hasOwnProperty('max') &&
zoom.hasOwnProperty('step') &&
zoom.hasOwnProperty('scale')
}
},
data() {
return {
img: null,
isMouseDown: false,
startX: null,
startY: null,
dx: 0,
dy: 0,
width: 427,
height: 610
}
},
watch: {
'zoom.scale'() {
this.draw()
}
},
methods: {
draw() {
const canvas = this.$refs.canvas
const ctx = canvas.getContext('2d')
ctx.clearRect(0, 0, this.width, this.height)
ctx.setTransform(
this.zoom.scale,
0,
0,
this.zoom.scale,
((1 - this.zoom.scale) * this.width) / 2,
((1 - this.zoom.scale) * this.height) / 2
)
ctx.drawImage(this.img, 0, 0, this.width, this.height)
},
loadImage(src) {
return new Promise((resolve, reject) => {
const img = new Image()
img.addEventListener('load', () => resolve(img))
img.addEventListener('error', err => reject(err))
img.src = src
})
},
onMouseDown({ clientX, clientY }) {
this.startX = clientX
this.startY = clientY
this.isMouseDown = true
},
onMouseMove({ clientX, clientY }) {
if (this.isMouseDown) {
this.dx += clientX - this.startX
this.dy += clientY - this.startY
this.draw()
this.startX = clientX
this.startY = clientY
}
},
onMouseUp() {
this.isMouseDown = false
this.dx = 0
this.dy = 0
},
onMouseOut() {
this.isMouseDown = false
},
onMouseWheel({ offsetX, offsetY, deltaY }) {
this.dx = -offsetX
this.dy = -offsetY
this.$emit(
'scale-change',
Math.min(this.zoom.max, Math.max(this.zoom.min, this.zoom.scale - deltaY * this.zoom.step))
)
}
},
mounted() {
this.loadImage(this.src).then(img => {
this.img = img
this.$nextTick(() => {
this.draw()
})
})
}
}
</script>
I noticed that in some cases the images are affected by strange artifacts, take a look at this example: https://jsfiddle.net/lmartini/b3hLr5ej/latest/
In the example, while zooming-in, I can clearly see a bunch of horizontal bands along the whole fabric height that disappear after a certain zoom level.
By googling I believe that these artifacts are caused by the resolution mismatch between the canvas and the image (the so called pixel-perfect problem) and, hence, by the internal browser downsampling algorithm.
I tried to improve the image smoothing quality of the canvas by adding the following lines but they didn't make any difference at all (my target browser is Chrome):
// ctx is the canvas context
ctx.imageSmoothingEnabled = true
ctx.imageSmoothingQuality = 'high'
How can I get rid of these artifacts?

Smooth 60 fps video from konva canvas animation using ccapturejs

Hey everyone so I have a Konvajs application that works great as a video editor running on the vuejs library. However, I want to capture the canvas and create a seamless video at 60 fps. In order to do this, I am trying to utilize the CCapturejs library. It kind of works except for now the playback of the webm is really fast and still a bit choppy. Can any of ya'll look at this code and help me find the problem? Thanks.
<template>
<div>
<button #click="render">Render</button>
<button #click="stop">stop</button>
<h2>Backgrounds</h2>
<template v-for="background in backgrounds">
<img
:src="background.poster"
class="backgrounds"
#click="changeBackground(background.video)"
/>
</template>
<h2>Images</h2>
<template v-for="image in images">
<img
:src="image.source"
#click="addImage(image)"
class="images"
/>
</template>
<br />
<button #click="addText">Add Text</button>
<button v-if="selectedNode" #click="removeNode">
Remove selected {{ selectedNode.type }}
</button>
<label>Font:</label>
<select v-model="selectedFont">
<option value="Arial">Arial</option>
<option value="Courier New">Courier New</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Desoto">Desoto</option>
<option value="Kalam">Kalam</option>
</select>
<label>Font Size</label>
<input type="number" v-model="selectedFontSize" />
<label>Font Style:</label>
<select v-model="selectedFontStyle">
<option value="normal">Normal</option>
<option value="bold">Bold</option>
<option value="italic">Italic</option>
</select>
<label>Color:</label>
<input type="color" v-model="selectedColor" />
<button
v-if="selectedNode && selectedNode.type === 'text'"
#click="updateText"
>
Update Text
</button>
<template v-if="selectedNode && selectedNode.lottie">
<input type="text" v-model="text">
<button #click="updateAnim(selectedNode.image)">
Update Animation
</button>
</template>
<br />
<video
id="preview"
v-show="preview"
:src="preview"
:width="width"
:height="height"
preload="auto"
controls
/>
<a v-if="file" :href="file" download="dopeness.mp4">download</a>
<div id="container"></div>
</div>
</template>
<script>
import lottie from "lottie-web";
import CCapture from "../ccapture";
import * as anim from "../AEAnim/anim.json";
import * as anim2 from "../AEAnim/anim2.json";
import * as anim3 from "../AEAnim/anim3.json";
import * as anim4 from "../AEAnim/anim4.json";
import * as anim5 from "../AEAnim/anim5.json";
export default {
data() {
return {
source: null,
stage: null,
layer: null,
video: null,
animations: [],
text: "",
animationData: null,
captures: [],
capturer: null,
backgrounds: [
{
poster: "/api/files/stock/3oref310k1uud86w/poster/poster.jpg",
video:
"/api/files/stock/3oref310k1uud86w/main/1080/3oref310k1uud86w_1080.mp4"
},
{
poster: "https://i2.wp.com/livinglifefearless.co/wp-content/uploads/2019/08/Forrest-Gump-25-1.jpg?fit=1920%2C1220&ssl=1",
video: "/api/files/jedi/run_forest_run.mp4"
},
{
poster: "/api/files/stock/3yj2e30tk5x6x0ww/poster/poster.jpg",
video:
"/api/files/stock/3yj2e30tk5x6x0ww/main/1080/3yj2e30tk5x6x0ww_1080.mp4"
},
{
poster: "/api/files/stock/2ez931ik1mggd6j/poster/poster.jpg",
video:
"/api/files/stock/2ez931ik1mggd6j/main/1080/2ez931ik1mggd6j_1080.mp4"
},
{
poster: "/api/files/stock/yxrt4ej4jvimyk15/poster/poster.jpg",
video:
"/api/files/stock/yxrt4ej4jvimyk15/main/1080/yxrt4ej4jvimyk15_1080.mp4"
},
{
poster:
"https://images.costco-static.com/ImageDelivery/imageService?profileId=12026540&itemId=100424771-847&recipeName=680",
video: "/api/files/jedi/surfing.mp4"
},
{
poster:
"https://thedefensepost.com/wp-content/uploads/2018/04/us-soldiers-afghanistan-4308413-1170x610.jpg",
video: "/api/files/jedi/soldiers.mp4"
}
],
images: [
{ source: "/api/files/jedi/solo.jpg" },
{ source: "api/files/jedi/yoda.jpg" },
{ source: "api/files/jedi/yodaChristmas.jpg" },
{ source: "api/files/jedi/darthMaul.jpg" },
{ source: "api/files/jedi/darthMaul1.jpg" },
{ source: "api/files/jedi/trump.jpg" },
{ source: "api/files/jedi/hat.png" },
{ source: "api/files/jedi/trump.png" },
{ source: "api/files/jedi/bernie.png" },
{ source: "api/files/jedi/skywalker.png" },
{ source: "api/files/jedi/vader.gif" },
{ source: "api/files/jedi/vader2.gif" },
{ source: "api/files/jedi/yoda.gif" },
{ source: "api/files/jedi/kylo.gif" },
{
source: "https://media3.giphy.com/media/R3IxJW14a3QNa/source.gif",
animation: anim
},
{
source: "https://bestanimations.com/Text/Cool/cool-story-3.gif",
animation: anim2
},
{
source: "https://freefrontend.com/assets/img/css-text-animations/HTML-CSS-Animated-Text-Fill.gif",
animation: anim3
},
{
source: "api/files/jedi/slider.gif",
animation: anim4
},
{
source: "api/files/jedi/zoomer.gif",
animation: anim5
}
],
backgroundVideo: null,
imageGroups: [],
anim: null,
selectedNode: null,
selectedFont: "Arial",
selectedColor: "black",
selectedFontSize: 20,
selectedFontStyle: "normal",
width: 1920,
height: 1080,
texts: [],
preview: null,
file: null,
canvas: null
};
},
mounted: function() {
this.initCanvas();
},
methods: {
changeBackground(source) {
this.source = source;
this.video.src = this.source;
this.anim.stop();
this.anim.start();
this.video.play();
},
removeNode() {
if (this.selectedNode && this.selectedNode.type === "text") {
this.selectedNode.transformer.destroy(
this.selectedNode.text.transformer
);
this.selectedNode.text.destroy(this.selectedNode.text);
this.texts.splice(this.selectedNode.text.index - 1, 1);
this.selectedNode = null;
this.layer.draw();
} else if (this.selectedNode && this.selectedNode.type == "image") {
this.selectedNode.group.destroy(this.selectedNode);
this.imageGroups.splice(this.selectedNode.group.index - 1, 1);
if (this.selectedNode.lottie) {
cancelAnimationFrame(this.animations.animFrame);
this.selectedNode.lottie.destroy();
this.animations.splice(this.selectedNode.lottie.index - 1, 1);
}
this.selectedNode = null;
this.layer.draw();
}
},
async addImage(imageToAdd, isUpdate) {
let lottieAnimation = null;
let imageObj = null;
const type = imageToAdd.source.slice(imageToAdd.source.lastIndexOf("."));
const vm = this;
function process(img) {
return new Promise((resolve, reject) => {
img.onload = () => resolve({ width: img.width, height: img.height });
});
}
imageObj = new Image();
imageObj.src = imageToAdd.source;
imageObj.width = 200;
imageObj.height = 200;
await process(imageObj);
if (type === ".gif" && !imageToAdd.animation) {
const canvas = document.createElement("canvas");
canvas.setAttribute("id", "gif");
async function onDrawFrame(ctx, frame) {
ctx.drawImage(frame.buffer, frame.x, frame.y);
// redraw the layer
vm.layer.draw();
}
gifler(imageToAdd.source).frames(canvas, onDrawFrame);
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
imageObj = canvas;
const gif = new Image();
gif.src = imageToAdd.source;
const gifImage = await process(gif);
imageObj.width = gifImage.width;
imageObj.height = gifImage.height;
} else if (imageToAdd.animation) {
if(!isUpdate){this.text = "new text";}
const canvas = document.createElement("canvas");
canvas.style.width = 1920;
canvas.style.height= 1080;
canvas.setAttribute("id", "animationCanvas");
const ctx = canvas.getContext("2d");
const div = document.createElement("div");
div.setAttribute("id", "animationContainer");
div.style.display = "none";
canvas.style.display = "none";
this.animationData = imageToAdd.animation.default;
for(let i =0; i <this.animationData.layers.length; i++){
for(let b =0; b<this.animationData.layers[i].t.d.k.length; b++){
this.animationData.layers[i].t.d.k[b].s.t = this.text;
}
}
lottieAnimation = lottie.loadAnimation({
container: div, // the dom element that will contain the animation
renderer: "svg",
loop: true,
autoplay: true,
animationData: this.animationData
});
lottieAnimation.imgSrc = imageToAdd.source;
lottieAnimation.text = this.text;
const svg = await div.getElementsByTagName("svg")[0];
async function updateSvg() {
const xml = new XMLSerializer().serializeToString(svg);
const svg64 = window.btoa(xml);
const b64Start = "data:image/svg+xml;base64,";
const image64 = b64Start + svg64;
imageObj = new Image({ width: canvas.width, height: canvas.height });
imageObj.src = image64;
await process(imageObj);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
vm.layer.draw();
updateSvg();
};
const animFrame = requestAnimationFrame(updateSvg);
this.animations.push({ lottie: lottieAnimation, animFrame });
imageObj = canvas;
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
}
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
width: imageObj.width,
height: imageObj.height,
position: (0, 0),
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
const group = new Konva.Group({
draggable: true
});
// add the shape to the layer
addAnchor(group, 0, 0, "topLeft");
addAnchor(group, imageObj.width, 0, "topRight");
addAnchor(group, imageObj.width, imageObj.height, "bottomRight");
addAnchor(group, 0, imageObj.height, "bottomLeft");
imageObj = null;
image.on("click", function () {
vm.hideAllHelpers();
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation,
image: imageToAdd
};
if(lottieAnimation && lottieAnimation.text){vm.text = lottieAnimation.text}
group.find("Circle").show();
vm.layer.draw();
});
image.on("mouseover", function(evt) {
if (vm.selectedNode && vm.selectedNode.type === "image") {
const index = image.getParent().index;
const groupId = vm.selectedNode.group.index;
if (index != groupId) {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
} else {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
});
image.on("mouseout", function(evt) {
evt.target.strokeEnabled(false);
vm.layer.draw();
});
vm.hideAllHelpers();
group.find("Circle").show();
group.add(image);
vm.layer.add(group);
vm.imageGroups.push(group);
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation,
image: imageToAdd
};
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let stage = vm.stage;
let layer = vm.layer;
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function() {
group.draggable(true);
layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
let layer = this.getLayer();
document.body.style.cursor = "pointer";
this.strokeWidth(4);
layer.draw();
});
anchor.on("mouseout", function() {
let layer = this.getLayer();
document.body.style.cursor = "default";
this.strokeWidth(2);
layer.draw();
});
group.add(anchor);
}
},
async updateAnim(image){
this.addImage(image, true);
this.removeNode();
},
hideAllHelpers() {
for (let i = 0; i < this.texts.length; i++) {
this.texts[i].transformer.hide();
}
for (let b = 0; b < this.imageGroups.length; b++) {
this.imageGroups[b].find("Circle").hide();
}
},
render(){
this.video.currentTime =0;
this.video.loop = false;
this.captureCanvas();
},
captureCanvas (){
this.capturer = new CCapture( {
verbose: true,
//display: false,
framerate: 60,
//motionBlurFrames: 0,
//quality: 100,
format: "webm",
timeLimit: 0
//frameLimit: 0,
//autoSaveTime: 0,
} );
this.capturer.start();
this.updateCapturer();
},
stop(){
this.capturer.stop();
this.capturer.save();
},
updateCapturer(){
console.log("holy crap I was called");
console.log("this is the canvas", this.canvas);
let rAF;
if(this.capturer){
this.capturer.capture(this.canvas)
rAF = requestAnimationFrame(this.updateCapturer);
} else {
cancelAnimationFrame(rAf);
}
},
updateText() {
if (this.selectedNode && this.selectedNode.type === "text") {
const text = this.selectedNode.text;
const transformer = this.selectedNode.transformer;
text.fontSize(this.selectedFontSize);
text.fontFamily(this.selectedFont);
text.fontStyle(this.selectedFontStyle);
text.fill(this.selectedColor);
this.layer.draw();
}
},
addText() {
const vm = this;
const text = new Konva.Text({
text: "new text " + (vm.texts.length + 1),
x: 50,
y: 80,
fontSize: this.selectedFontSize,
fontFamily: this.selectedFont,
fontStyle: this.selectedFontStyle,
fill: this.selectedColor,
align: "center",
width: this.width * 0.5,
draggable: true
});
const transformer = new Konva.Transformer({
node: text,
keepRatio: true,
enabledAnchors: ["top-left", "top-right", "bottom-left", "bottom-right"]
});
text.on("click", async () => {
for (let i = 0; i < this.texts.length; i++) {
let item = this.texts[i];
if (item.index === text.index) {
let transformer = item.transformer;
this.selectedNode = { type: "text", text, transformer };
this.selectedFontSize = text.fontSize();
this.selectedFont = text.fontFamily();
this.selectedFontStyle = text.fontStyle();
this.selectedColor = text.fill();
vm.hideAllHelpers();
transformer.show();
transformer.moveToTop();
text.moveToTop();
vm.layer.draw();
break;
}
}
});
text.on("mouseover", () => {
transformer.show();
this.layer.draw();
});
text.on("mouseout", () => {
if (
(this.selectedNode &&
this.selectedNode.text &&
this.selectedNode.text.index != text.index) ||
(this.selectedNode && this.selectedNode.type === "image") ||
!this.selectedNode
) {
transformer.hide();
this.layer.draw();
}
});
text.on("dblclick", () => {
text.hide();
transformer.hide();
vm.layer.draw();
let textPosition = text.absolutePosition();
let stageBox = vm.stage.container().getBoundingClientRect();
let areaPosition = {
x: stageBox.left + textPosition.x,
y: stageBox.top + textPosition.y
};
let textarea = document.createElement("textarea");
window.document.body.appendChild(textarea);
textarea.value = text.text();
textarea.style.position = "absolute";
textarea.style.top = areaPosition.y + "px";
textarea.style.left = areaPosition.x + "px";
textarea.style.width = text.width() - text.padding() * 2 + "px";
textarea.style.height = text.height() - text.padding() * 2 + 5 + "px";
textarea.style.fontSize = text.fontSize() + "px";
textarea.style.border = "none";
textarea.style.padding = "0px";
textarea.style.margin = "0px";
textarea.style.overflow = "hidden";
textarea.style.background = "none";
textarea.style.outline = "none";
textarea.style.resize = "none";
textarea.style.lineHeight = text.lineHeight();
textarea.style.fontFamily = text.fontFamily();
textarea.style.transformOrigin = "left top";
textarea.style.textAlign = text.align();
textarea.style.color = text.fill();
let rotation = text.rotation();
let transform = "";
if (rotation) {
transform += "rotateZ(" + rotation + "deg)";
}
let px = 0;
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox) {
px += 2 + Math.round(text.fontSize() / 20);
}
transform += "translateY(-" + px + "px)";
textarea.style.transform = transform;
textarea.style.height = "auto";
textarea.focus();
// start
function removeTextarea() {
textarea.parentNode.removeChild(textarea);
window.removeEventListener("click", handleOutsideClick);
text.show();
transformer.show();
transformer.forceUpdate();
vm.layer.draw();
}
function setTextareaWidth(newWidth) {
if (!newWidth) {
// set width for placeholder
newWidth = text.placeholder.length * text.fontSize();
}
// some extra fixes on different browsers
let isSafari = /^((?!chrome|android).)*safari/i.test(
navigator.userAgent
);
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isSafari || isFirefox) {
newWidth = Math.ceil(newWidth);
}
let isEdge =
document.documentMode || /Edge/.test(navigator.userAgent);
if (isEdge) {
newWidth += 1;
}
textarea.style.width = newWidth + "px";
}
textarea.addEventListener("keydown", function(e) {
// hide on enter
// but don't hide on shift + enter
if (e.keyCode === 13 && !e.shiftKey) {
text.text(textarea.value);
removeTextarea();
}
// on esc do not set value back to node
if (e.keyCode === 27) {
removeTextarea();
}
});
textarea.addEventListener("keydown", function(e) {
let scale = text.getAbsoluteScale().x;
setTextareaWidth(text.width() * scale);
textarea.style.height = "auto";
textarea.style.height =
textarea.scrollHeight + text.fontSize() + "px";
});
function handleOutsideClick(e) {
if (e.target !== textarea) {
text.text(textarea.value);
removeTextarea();
}
}
setTimeout(() => {
window.addEventListener("click", handleOutsideClick);
});
// end
});
text.transformer = transformer;
this.texts.push(text);
this.layer.add(text);
this.layer.add(transformer);
this.hideAllHelpers();
this.selectedNode = { type: "text", text, transformer };
transformer.show();
this.layer.draw();
},
initCanvas() {
const vm = this;
this.stage = new Konva.Stage({
container: "container",
width: vm.width,
height: vm.height
});
this.layer = new Konva.Layer();
this.stage.add(this.layer);
let video = document.createElement("video");
video.setAttribute("id", "video");
video.setAttribute("ref", "video");
if (this.source) {
video.src = this.source;
}
video.preload = "auto";
video.loop = "loop";
video.style.display = "none";
this.video = video;
this.backgroundVideo = new Konva.Image({
image: vm.video,
draggable: false
});
this.video.addEventListener("loadedmetadata", function(e) {
vm.backgroundVideo.width(vm.width);
vm.backgroundVideo.height(vm.height);
});
this.video.addEventListener("ended", () => {
console.log("the video ended");
this.stop();
this.capturer = null;
this.anim.stop();
this.anim.start();
this.video.loop = "loop";
this.video.play();
});
this.anim = new Konva.Animation(function() {
console.log("animation called");
// do nothing, animation just need to update the layer
}, vm.layer);
this.layer.add(this.backgroundVideo);
this.layer.draw();
const canvas = document.getElementsByTagName("canvas")[0];
canvas.style.border = "3px solid red";
this.canvas = canvas;
}
}
};
</script>
<style scoped>
body {
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
.backgrounds,
.images {
width: 100px;
height: 100px;
padding-left: 2px;
padding-right: 2px;
}
</style>
Use this code to pause the video and take screenshots. You can than use Whammy to generate a webm and convert it to whatever file format you like with ffmpeg
generateVideo(){
const vid = new Whammy.fromImageArray(this.captures, 30);
vid.name = "project_id_238.webm";
vid.lastModifiedDate = new Date();
this.file = URL.createObjectURL(vid);
},
async pauseAll(){
this.pauseVideo();
if(this.animations.length){
this.pauseLotties()
}
this.captures.push(this.canvas.toDataURL('image/webp'));
if(!this.ended){
setTimeout(()=>{
this.pauseAll();
}, 500);
}
},
async pauseVideo(){
console.log("curretTime",this.video.currentTime);
console.log("duration", this.video.duration);
this.video.pause();
const oneFrame = 1/30;
this.video.currentTime += oneFrame;
},
async pauseLotties(){
lottie.freeze();
for(let i =0; i<this.animations.length; i++){
let step =0;
let animation = this.animations[i].lottie;
if(animation.currentFrame<=animation.totalFrames){
step = animation.currentFrame + animation.totalFrames/30;
}
lottie.goToAndStop(step, true, animation.name);
}
},

How to connect 2 objects using a line using konvajs in vuejs?

Good morning, I find myself working with the Konvajs library, https://github.com/konvajs/vue-konva
There is the following documentation: https://konvajs.org/docs/sandbox/Connected_Objects.html, but I can't implement it with vuejs
Since what I need to do is that when selecting object 1, I can drag and form the arrow and when selecting object 2, they are linked
Currently I have built the following:
<template>
<v-container>
<v-stage :config="configKonva">
<v-layer>
<v-circle :config="configCircle"></v-circle>
</v-layer>
<v-layer>
<v-circle :config="configCircleA"></v-circle>
</v-layer>
</v-stage>
</v-container>
</template>
<script>
export default {
data(){
return {
configKonva: {
width: 200,
height: 200
},
configCircle: {
x: 100,
y: 100,
radius: 70,
fill: "red",
stroke: "black",
strokeWidth: 4,
draggable: true
},
configCircleA: {
x: 100,
y: 100,
radius: 70,
fill: "green",
stroke: "black",
strokeWidth: 4,
draggable: true
}
}
},
}
</script>
Visually I have only created the circles, I lack the connection of these 2 through a line
There are many ways to implement such functionality. Basically, you just need to listen to mousedown, mousemove and mouseup events to understand when to draw lines. You can also add touchstart, touchmove and touchend events to support mobile devices:
<template>
<div>
<v-stage
ref="stage"
:config="stageSize"
#mousedown="handleMouseDown"
#mouseup="handleMouseUp"
#mousemove="handleMouseMove"
>
<v-layer>
<v-line
v-for="line in connections"
:key="line.id"
:config="{
stroke: 'black',
points: line.points
}"
/>
<v-circle
v-for="target in targets"
:key="target.id"
:config="{
x: target.x,
y: target.y,
radius: 40,
stroke: 'black',
fill: 'green'
}"
/>
<v-text :config="{ text: 'Try to drag-to-connect objects'}"/>
</v-layer>
<v-layer ref="dragLayer"></v-layer>
</v-stage>
</div>
</template>
<script>
import Konva from "konva";
const width = window.innerWidth;
const height = window.innerHeight;
let vm = {};
function generateTargets() {
const circles = [];
for (var i = 0; i < 10; i++) {
circles.push({
x: width * Math.random(),
y: height * Math.random(),
id: i
});
}
return circles;
}
export default {
data() {
return {
stageSize: {
width: width,
height: height
},
targets: generateTargets(),
connections: [],
drawningLine: false
};
},
methods: {
handleMouseDown(e) {
const onCircle = e.target instanceof Konva.Circle;
if (!onCircle) {
return;
}
this.drawningLine = true;
this.connections.push({
id: Date.now(),
points: [e.target.x(), e.target.y()]
});
},
handleMouseMove(e) {
if (!this.drawningLine) {
return;
}
const pos = e.target.getStage().getPointerPosition();
const lastLine = this.connections[this.connections.length - 1];
lastLine.points = [lastLine.points[0], lastLine.points[1], pos.x, pos.y];
},
handleMouseUp(e) {
const onCircle = e.target instanceof Konva.Circle;
if (!onCircle) {
return;
}
this.drawningLine = false;
const lastLine = this.connections[this.connections.length - 1];
lastLine.points = [
lastLine.points[0],
lastLine.points[1],
e.target.x(),
e.target.y()
];
}
}
};
</script>
DEmo: https://codesandbox.io/s/vue-konva-connection-objects-qk2ps

add Searchbox to polygon drawing google map

I am using this below script to draw polygon to google map, which is working ok. I want to add searchbox to map. I tried a lot, But cannot embed searchbox.
http://bl.ocks.org/knownasilya/89a32e572989f0aff1f8
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="UTF-8">
<title>Drawing Tools</title>
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?sensor=false&libraries=drawing"></script>
<style type="text/css">
#map, html, body {
padding: 0;
margin: 0;
width: 960px;
height: 700px;
}
#panel {
width: 200px;
font-family: Arial, sans-serif;
font-size: 13px;
float: right;
margin: 10px;
}
#color-palette {
clear: both;
}
.color-button {
width: 14px;
height: 14px;
font-size: 0;
margin: 2px;
float: left;
cursor: pointer;
}
#delete-button {
margin-top: 5px;
}
</style>
<script type="text/javascript">
var drawingManager;
var selectedShape;
var colors = ['#1E90FF', '#FF1493', '#32CD32', '#FF8C00', '#4B0082'];
var selectedColor;
var colorButtons = {};
function clearSelection () {
if (selectedShape) {
if (selectedShape.type !== 'marker') {
selectedShape.setEditable(false);
}
selectedShape = null;
}
}
function setSelection (shape) {
if (shape.type !== 'marker') {
clearSelection();
shape.setEditable(true);
selectColor(shape.get('fillColor') || shape.get('strokeColor'));
}
selectedShape = shape;
}
function deleteSelectedShape () {
if (selectedShape) {
selectedShape.setMap(null);
}
}
function selectColor (color) {
selectedColor = color;
for (var i = 0; i < colors.length; ++i) {
var currColor = colors[i];
colorButtons[currColor].style.border = currColor == color ? '2px solid #789' : '2px solid #fff';
}
// Retrieves the current options from the drawing manager and replaces the
// stroke or fill color as appropriate.
var polylineOptions = drawingManager.get('polylineOptions');
polylineOptions.strokeColor = color;
drawingManager.set('polylineOptions', polylineOptions);
var rectangleOptions = drawingManager.get('rectangleOptions');
rectangleOptions.fillColor = color;
drawingManager.set('rectangleOptions', rectangleOptions);
var circleOptions = drawingManager.get('circleOptions');
circleOptions.fillColor = color;
drawingManager.set('circleOptions', circleOptions);
var polygonOptions = drawingManager.get('polygonOptions');
polygonOptions.fillColor = color;
drawingManager.set('polygonOptions', polygonOptions);
}
function setSelectedShapeColor (color) {
if (selectedShape) {
if (selectedShape.type == google.maps.drawing.OverlayType.POLYLINE) {
selectedShape.set('strokeColor', color);
} else {
selectedShape.set('fillColor', color);
}
}
}
function makeColorButton (color) {
var button = document.createElement('span');
button.className = 'color-button';
button.style.backgroundColor = color;
google.maps.event.addDomListener(button, 'click', function () {
selectColor(color);
setSelectedShapeColor(color);
});
return button;
}
function buildColorPalette () {
var colorPalette = document.getElementById('color-palette');
for (var i = 0; i < colors.length; ++i) {
var currColor = colors[i];
var colorButton = makeColorButton(currColor);
colorPalette.appendChild(colorButton);
colorButtons[currColor] = colorButton;
}
selectColor(colors[0]);
}
function initialize () {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 16,
center: new google.maps.LatLng(52.25097, 20.97114),
mapTypeId: google.maps.MapTypeId.SATELLITE,
disableDefaultUI: true,
zoomControl: true
});
var polyOptions = {
strokeWeight: 0,
fillOpacity: 0.45,
editable: true,
draggable: true
};
// Creates a drawing manager attached to the map that allows the user to draw
// markers, lines, and shapes.
drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.POLYGON,
markerOptions: {
draggable: true
},
polylineOptions: {
editable: true,
draggable: true
},
rectangleOptions: polyOptions,
circleOptions: polyOptions,
polygonOptions: polyOptions,
map: map
});
google.maps.event.addListener(drawingManager, 'overlaycomplete', function (e) {
var newShape = e.overlay;
newShape.type = e.type;
if (e.type !== google.maps.drawing.OverlayType.MARKER) {
// Switch back to non-drawing mode after drawing a shape.
drawingManager.setDrawingMode(null);
// Add an event listener that selects the newly-drawn shape when the user
// mouses down on it.
google.maps.event.addListener(newShape, 'click', function (e) {
if (e.vertex !== undefined) {
if (newShape.type === google.maps.drawing.OverlayType.POLYGON) {
var path = newShape.getPaths().getAt(e.path);
path.removeAt(e.vertex);
if (path.length < 3) {
newShape.setMap(null);
}
}
if (newShape.type === google.maps.drawing.OverlayType.POLYLINE) {
var path = newShape.getPath();
path.removeAt(e.vertex);
if (path.length < 2) {
newShape.setMap(null);
}
}
}
setSelection(newShape);
});
setSelection(newShape);
}
else {
google.maps.event.addListener(newShape, 'click', function (e) {
setSelection(newShape);
});
setSelection(newShape);
}
});
// Clear the current selection when the drawing mode is changed, or when the
// map is clicked.
google.maps.event.addListener(drawingManager, 'drawingmode_changed', clearSelection);
google.maps.event.addListener(map, 'click', clearSelection);
google.maps.event.addDomListener(document.getElementById('delete-button'), 'click', deleteSelectedShape);
buildColorPalette();
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="panel">
<div id="color-palette"></div>
<div>
<button id="delete-button">Delete Selected Shape</button>
</div>
</div>
<div id="map"></div>
</body>
</html>
I want to add searchbox with this script, After trying a lot I posted it. thank you for your precious time.
From the SearchBox example in the documentation
add the following code to your initialize function (be sure to include the places library in the API include):
// Create the search box and link it to the UI element.
var input = document.getElementById('pac-input');
var searchBox = new google.maps.places.SearchBox(input);
map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
searchBox.setBounds(map.getBounds());
});
var markers = [];
// Listen for the event fired when the user selects a prediction and retrieve
// more details for that place.
searchBox.addListener('places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// Clear out the old markers.
markers.forEach(function(marker) {
marker.setMap(null);
});
markers = [];
// For each place, get the icon, name and location.
var bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
if (!place.geometry) {
console.log("Returned place contains no geometry");
return;
}
var icon = {
url: place.icon,
size: new google.maps.Size(71, 71),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(17, 34),
scaledSize: new google.maps.Size(25, 25)
};
// Create a marker for each place.
markers.push(new google.maps.Marker({
map: map,
icon: icon,
title: place.name,
position: place.geometry.location
}));
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
} else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
proof of concept fiddle
code snippet:
var drawingManager;
var selectedShape;
var colors = ['#1E90FF', '#FF1493', '#32CD32', '#FF8C00', '#4B0082'];
var selectedColor;
var colorButtons = {};
function clearSelection() {
if (selectedShape) {
if (selectedShape.type !== 'marker') {
selectedShape.setEditable(false);
}
selectedShape = null;
}
}
function setSelection(shape) {
if (shape.type !== 'marker') {
clearSelection();
shape.setEditable(true);
selectColor(shape.get('fillColor') || shape.get('strokeColor'));
}
selectedShape = shape;
}
function deleteSelectedShape() {
if (selectedShape) {
selectedShape.setMap(null);
}
}
function selectColor(color) {
selectedColor = color;
for (var i = 0; i < colors.length; ++i) {
var currColor = colors[i];
colorButtons[currColor].style.border = currColor == color ? '2px solid #789' : '2px solid #fff';
}
// Retrieves the current options from the drawing manager and replaces the
// stroke or fill color as appropriate.
var polylineOptions = drawingManager.get('polylineOptions');
polylineOptions.strokeColor = color;
drawingManager.set('polylineOptions', polylineOptions);
var rectangleOptions = drawingManager.get('rectangleOptions');
rectangleOptions.fillColor = color;
drawingManager.set('rectangleOptions', rectangleOptions);
var circleOptions = drawingManager.get('circleOptions');
circleOptions.fillColor = color;
drawingManager.set('circleOptions', circleOptions);
var polygonOptions = drawingManager.get('polygonOptions');
polygonOptions.fillColor = color;
drawingManager.set('polygonOptions', polygonOptions);
}
function setSelectedShapeColor(color) {
if (selectedShape) {
if (selectedShape.type == google.maps.drawing.OverlayType.POLYLINE) {
selectedShape.set('strokeColor', color);
} else {
selectedShape.set('fillColor', color);
}
}
}
function makeColorButton(color) {
var button = document.createElement('span');
button.className = 'color-button';
button.style.backgroundColor = color;
google.maps.event.addDomListener(button, 'click', function() {
selectColor(color);
setSelectedShapeColor(color);
});
return button;
}
function buildColorPalette() {
var colorPalette = document.getElementById('color-palette');
for (var i = 0; i < colors.length; ++i) {
var currColor = colors[i];
var colorButton = makeColorButton(currColor);
colorPalette.appendChild(colorButton);
colorButtons[currColor] = colorButton;
}
selectColor(colors[0]);
}
function initialize() {
var map = new google.maps.Map(document.getElementById('map'), {
zoom: 16,
center: new google.maps.LatLng(52.25097, 20.97114),
mapTypeId: google.maps.MapTypeId.SATELLITE,
disableDefaultUI: true,
zoomControl: true
});
var polyOptions = {
strokeWeight: 0,
fillOpacity: 0.45,
editable: true,
draggable: true
};
// Creates a drawing manager attached to the map that allows the user to draw
// markers, lines, and shapes.
drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: google.maps.drawing.OverlayType.POLYGON,
markerOptions: {
draggable: true
},
polylineOptions: {
editable: true,
draggable: true
},
rectangleOptions: polyOptions,
circleOptions: polyOptions,
polygonOptions: polyOptions,
map: map
});
google.maps.event.addListener(drawingManager, 'overlaycomplete', function(e) {
var newShape = e.overlay;
newShape.type = e.type;
if (e.type !== google.maps.drawing.OverlayType.MARKER) {
// Switch back to non-drawing mode after drawing a shape.
drawingManager.setDrawingMode(null);
// Add an event listener that selects the newly-drawn shape when the user
// mouses down on it.
google.maps.event.addListener(newShape, 'click', function(e) {
if (e.vertex !== undefined) {
if (newShape.type === google.maps.drawing.OverlayType.POLYGON) {
var path = newShape.getPaths().getAt(e.path);
path.removeAt(e.vertex);
if (path.length < 3) {
newShape.setMap(null);
}
}
if (newShape.type === google.maps.drawing.OverlayType.POLYLINE) {
var path = newShape.getPath();
path.removeAt(e.vertex);
if (path.length < 2) {
newShape.setMap(null);
}
}
}
setSelection(newShape);
});
setSelection(newShape);
} else {
google.maps.event.addListener(newShape, 'click', function(e) {
setSelection(newShape);
});
setSelection(newShape);
}
});
// Clear the current selection when the drawing mode is changed, or when the
// map is clicked.
google.maps.event.addListener(drawingManager, 'drawingmode_changed', clearSelection);
google.maps.event.addListener(map, 'click', clearSelection);
google.maps.event.addDomListener(document.getElementById('delete-button'), 'click', deleteSelectedShape);
buildColorPalette();
// SearchBox code
// Create the search box and link it to the UI element.
var input = document.getElementById('pac-input');
var searchBox = new google.maps.places.SearchBox(input);
map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
searchBox.setBounds(map.getBounds());
});
var markers = [];
// Listen for the event fired when the user selects a prediction and retrieve
// more details for that place.
searchBox.addListener('places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// Clear out the old markers.
markers.forEach(function(marker) {
marker.setMap(null);
});
markers = [];
// For each place, get the icon, name and location.
var bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
if (!place.geometry) {
console.log("Returned place contains no geometry");
return;
}
var icon = {
url: place.icon,
size: new google.maps.Size(71, 71),
origin: new google.maps.Point(0, 0),
anchor: new google.maps.Point(17, 34),
scaledSize: new google.maps.Size(25, 25)
};
// Create a marker for each place.
markers.push(new google.maps.Marker({
map: map,
icon: icon,
title: place.name,
position: place.geometry.location
}));
if (place.geometry.viewport) {
// Only geocodes have viewport.
bounds.union(place.geometry.viewport);
} else {
bounds.extend(place.geometry.location);
}
});
map.fitBounds(bounds);
});
}
google.maps.event.addDomListener(window, 'load', initialize);
#map,
html,
body {
padding: 0;
margin: 0;
width: 960px;
height: 700px;
}
#panel {
width: 200px;
font-family: Arial, sans-serif;
font-size: 13px;
float: right;
margin: 10px;
}
#color-palette {
clear: both;
}
.color-button {
width: 14px;
height: 14px;
font-size: 0;
margin: 2px;
float: left;
cursor: pointer;
}
#delete-button {
margin-top: 5px;
}
<script src="https://maps.googleapis.com/maps/api/js?libraries=geometry,places,drawing&key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk"></script>
<input id="pac-input" class="controls" type="text" placeholder="Search Box">
<div id="panel">
<div id="color-palette"></div>
<div>
<button id="delete-button">Delete Selected Shape</button>
</div>
</div>
<div id="map"></div>

Famo.us swipe on scrollview

I have go through the Timbre tutorial in Famo.us University and it works fine but when I add a scrollview to the layout.content the swipe function stop working the scrollview view works fine but not the swipe.
Anyone know how to aplly the swipe right function from the Trimbe tutorial with a scrollview?
///AppView///
function _handleSwipe() {
var sync = new GenericSync(
['mouse', 'touch'],
{direction : GenericSync.DIRECTION_X}
);
this.pageView.pipe(sync);
sync.on('update', function(data) {
var currentPosition = this.pageViewPos.get();
if(currentPosition === 0 && data.velocity > 0) {
this.menuView.animateStrips();
}
this.pageViewPos.set(Math.max(0, currentPosition + data.delta));
}.bind(this));
sync.on('end', (function(data) {
var velocity = data.velocity;
var position = this.pageViewPos.get();
if(this.pageViewPos.get() > this.options.posThreshold) {
if(velocity < -this.options.velThreshold) {
this.slideLeft();
} else {
this.slideRight();
}
} else {
if(velocity > this.options.velThreshold) {
this.slideRight();
} else {
this.slideLeft();
}
}
}).bind(this));
}
///PageView///
function _createBody() {
var surfaces = [];
this.scrollview = new Scrollview();
var temp;
for (var i = 0; i < 20; i++) {
temp = new Surface({
size: [undefined, 80],
content: 'I am surface: ' + (i + 1),
properties: {
textAlign: 'left',
lineHeight: '80px',
borderTop: '1px solid #f1f1f1',
borderBottom: '1px solid #fff',
backgroundColor: '#f9f9f9',
fontFamily: 'Arial',
backfaceVisibility: 'visible',
paddingLeft: '10px'
}
});
temp.pipe(this.scrollview);
surfaces.push(temp);
}
this.scrollview.sequenceFrom(surfaces);
this.bodyContent = new Surface({
size: [undefined, undefined],
properties: {
backgroundColor: '#f5f5f5'
}
});
//this.layout.content.add(this.bodyContent);
this.layoutContainer.add(this.scrollview);
}
function _setListeners() {
this.hamburgerSurface.on('click', function() {
this._eventOutput.emit('menuToggle');
}.bind(this));
//this.bodyContent.pipe(this._eventOutput);
this.scrollview.pipe(this._eventOutput);
}
I have figured out by myself if I put the pipe(this._eventOutPut) in the for each with the temp variable it works. I don't know if is the best solution so I will be happy if anyone can give a better exemplo.
for (var i = 0; i < 10; i++) {
temp = new Surface({
size: [undefined, 80],
content: 'I am surface: ' + (i + 1),
properties: {
textAlign: 'left',
lineHeight: '80px',
borderTop: '1px solid #f1f1f1',
backgroundColor: '#f9f9f9',
fontFamily: 'Arial',
backfaceVisibility: 'visible',
paddingLeft: '10px'
}
});
/// Add the ._eventOutput to the temp
temp.pipe(this._eventOutput);
temp.pipe(scrollview);
surfaces.push(temp);
}
You should indeed relay the events processed by the scrollview to your own sync.
I have created a SwipeSync to make this easier: http://famousco.de/2014/08/swipesync-famo-us/
var scrollview = {your scrollview};
var sync = new SwipeSync();
scrollview.pipe(sync);
sync.on('swipe', function(data) {
alert('Swiped to: ' + data.direction);
});