Multitouch in HTML5 Canvas / DOM for AngularJS - javascript

I started creating a little remote control App using Angular. I created a joystick directive which is using the canvas.
I wrapped it in an angular directive and it works quite nice. You can see an example here (NOTE: Only touch, no mouse events yet):
http://jsfiddle.net/wjE2B/3/
What i want to do is control a car which basicall means i will keep the joystick dragged all the time and additionally i want to control another button e.g. for the horn. I would prefer using a simple button here.
But all the handlers like ng-click stop working if i drag the joystick around. I can listen to other touch events (e.g. simply add another joystick, works independently) on the canvas and of course other canvas, but can i simply listen to other events like touch somehow? How do i achieve this the most angular / simplest way?
Here the code from the fiddle:
angular.module('myApp').directive('joystick', function() {
function joystickController ($scope) {
}
return {
restrict : 'E',
controller : ['$scope', function ($scope) {
return joystickController($scope);
}],
scope : {
// Using primitives here did not work, so we use an Object, see: http://stackoverflow.com/questions/14049480/what-are-the-nuances-of-scope-prototypal-prototypical-inheritance-in-angularjs
position : '='
},
template : '<canvas class="joystickCanvas"></canvas>',
link : function(scope, element) {
var joystickHeight = 200;
var joystickWidth = 200;
var center = {
x : joystickHeight / 2,
y : joystickWidth / 2
};
var radiusCircle = 35;
var radiusBound = 50;
// Canvas and context element
var container = element[0];
var canvas = container.children[0];
var ctx = canvas.getContext('2d');
// Id of the touch on the cursor
var cursorTouchId = -1;
var cursorTouch = {
x : center.x,
y : center.y
};
function resetCanvas() {
canvas.height = joystickHeight;
canvas.width = joystickWidth;
}
function onTouchStart(event) {
var touch = event.targetTouches[0];
cursorTouchId = touch.identifier;
cursorTouch = {
x : touch.pageX - touch.target.offsetLeft,
y : touch.pageY - touch.target.offsetTop
};
}
function onTouchMove(event) {
// Prevent the browser from doing its default thing (scroll, zoom)
event.preventDefault();
for(var i = 0; i < event.changedTouches.length; i++){
var touch = event.changedTouches[i];
if(cursorTouchId === touch.identifier)
{
cursorTouch = {
x : touch.pageX - touch.target.offsetLeft,
y : touch.pageY - touch.target.offsetTop
};
var scaleX = radiusBound / (cursorTouch.x - center.x);
var scaleY = radiusBound / (cursorTouch.y - center.y);
if(Math.abs(scaleX) < 1) {
cursorTouch.x = Math.abs(cursorTouch.x - center.x) * scaleX + center.x;
}
if (Math.abs(scaleY) < 1) {
cursorTouch.y = Math.abs(cursorTouch.y - center.y) * scaleY + center.y;
}
scope.$apply(
scope.position = {
x : Math.round(((cursorTouch.x - center.x)/radiusBound) * 100),
y : Math.round(((cursorTouch.y - center.y)/radiusBound) * -100)
}
);
break;
}
}
}
function onTouchEnd() {
cursorTouchId = -1;
scope.$apply(
scope.position = {
x : 0,
y : 0
}
);
cursorTouch.x = center.x;
cursorTouch.y = center.y;
}
function draw() {
// Clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.strokeStyle = 'cyan';
ctx.lineWidth = 5;
ctx.arc(center.x, center.y, radiusCircle, 0, Math.PI*2, true);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = 'cyan';
ctx.lineWidth = 2;
ctx.arc(center.x, center.y, radiusBound, 0, Math.PI*2, true);
ctx.stroke();
ctx.beginPath();
ctx.strokeStyle = 'cyan';
ctx.lineWidth = 2;
ctx.arc(cursorTouch.x, cursorTouch.y, radiusCircle, 0, Math.PI*2, true);
ctx.stroke();
requestAnimFrame(draw);
}
// Check if touch is enabled
var touchable = true;
if(touchable) {
canvas.addEventListener( 'touchstart', onTouchStart, false );
canvas.addEventListener( 'touchmove', onTouchMove, false );
canvas.addEventListener( 'touchend', onTouchEnd, false );
window.onorientationchange = resetCanvas;
window.onresize = resetCanvas;
}
// Bind to the values from outside as well
scope.$watch('position', function(newval) {
cursorTouch = {
x : ((newval.x * radiusBound) / 100) + center.x,
y : ((newval.y * radiusBound) / -100) + center.y
};
});
resetCanvas();
draw();
}
};
});

Related

how to fill particles inside rectangle?

I'm looking to draw a rectangle basically text but just for clearing insight I'm working it with rectangle with small particles inside rectangle the basic I idea I got from https://yalantis.com/ but in my attempt I'm stuck here with solid filled rectangle with a color I have specified for particles. Please help me.. :)
Thanks here is my code:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Off Screen Canvas</title>
<script>
function createOffscreenCanvas() {
var offScreenCanvas = document.createElement('canvas');
offScreenCanvas.width = '1360';
offScreenCanvas.height = '400';
var context = offScreenCanvas.getContext("2d");
var W=200;
var H=200;
particleCount = 200;
particles = []; //this is an array which will hold our particles Object/Class
function Particle() {
this.x = Math.random() * W;
this.y = Math.random() * H;
this.direction ={"x": -1 + Math.random()*2, "y": -1 + Math.random()*2};
this.vx = 2 * Math.random() + 4 ;
this.vy = 2 * Math.random() + 4;
this.radius = .9 * Math.random() + 1;
this.move = function(){
this.x += this.vx * this.direction.x;
this.y += this.vy * this.direction.y;
};
this.changeDirection = function(axis){
this.direction[axis] *= -1;
};
this.draw = function() {
context.beginPath();
context.fillStyle = "#0097a7";
context.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
context.fill();
};
this.boundaryCheck = function(){
if(this.x >= W){
this.x = W;
this.changeDirection("x");
}
else if(this.x <= 0){
this.x = 0;
this.changeDirection("x");
}
if(this.y >= H){
this.y = H;
this.changeDirection("y");
}
else if(this.y <= 0){
this.y = 0;
this.changeDirection("y");
}
};
}
function createParticles(){
for (var i = particleCount-1; i >= 0; i--) {
p = new Particle();
particles.push(p);
}
}// end createParticles
function drawParticles(){
for (var i = particleCount-1; i >= 0; i--){
p = particles[i];
p.draw();
}
} //end drawParticles
function updateParticles(){
for(var i = particles.length - 1; i >=0; i--){
p = particles[i];
p.move();
p.boundaryCheck();
}
}//end updateParticle
createParticles();
var part=drawParticles();
context.fillStyle=part;
context.fillRect(W-190, H-190, W, H);
context.fill();
return offScreenCanvas;
}
function copyToOnScreen(offScreenCanvas) {
var onScreenContext=document.getElementById('onScreen').getContext('2d');
var offScreenContext = offScreenCanvas.getContext('2d');
var image=offScreenContext.getImageData(10,10,200,200);
onScreenContext.putImageData(image,offScreenCanvas.width/2,offScreenCanvas.height/4);
}
function main() {
copyToOnScreen(createOffscreenCanvas());
}
</script>
<style>
canvas {
border: 1px solid red;
}
</style>
</head>
<body onload="main()">
<canvas id="onScreen" width="1360" height="400"></canvas>
</body>
</html>
I see you have not found what you are looking for yet. Below is something quick to get you on your way. There is a whole range of stuff being used from canvas,mouse,particles, etc most of which is without comments. There is no load balancing or compliance testing and because it uses babel to be compatible with IE11 I have no clue how it runs on those browsers.
I will add to this answer some other time but for now I am a little over it.
const textList = ["1","2","3","Testing","text","to","particles"];
var textPos = 0;
function createParticles(text){
createTextMap(
text, // text to display
40, // font size
"Arial", // font
{ // style fot rendering font
fillStyle : "#6AF",
strokeStyle : "#F80",
lineWidth : 2,
lineJoin : "round",
},{ // bounding box to find a best fit for
top : 0,
left : 0,
width : canvas.width,
height : canvas.height,
}
)
}
// This function starts the animations
var started = false;
function startIt(){
started = true;
const next = ()=>{
var text = textList[(textPos++ ) % textList.length];
particles.mouseFX.dist = canvas.height / 8;
createParticles(text);
setTimeout(moveOut,text.length * 100 + 3000);
}
const moveOut = ()=>{
particles.moveOut();
setTimeout(next,2000);
}
setTimeout(next,0);
}
function setStyle(ctx,style){
Object.keys(style).forEach(key => ctx[key] = style[key]);
}
// the following function create the particles from text using a canvas
// the canvas used is dsplayed on the main canvas top left fro referance.
var tCan = createImage(100, 100); // canvas used to draw text
function createTextMap(text,size,font,style,fit){
// function to conver to colour hex value
const hex = (v)=> (v < 16 ? "0" : "") + v.toString(16);
// set up font so we can find the size.
tCan.ctx.font = size + "px " + font;
// get size of text
var width = Math.ceil(tCan.ctx.measureText(text).width + size);
// resize the canvas to fit the text
tCan.width = width;
tCan.height = Math.ceil(size *1.2);
// c is alias for context
var c = tCan.ctx;
// set up font
c.font = size + "px " + font;
c.textAlign = "center";
c.textBaseline = "middle";
// set style
setStyle(c,style);
// only do stroke and fill if they are set in styles object
if(style.strokeStyle){
c.strokeText(text, width / 2, tCan.height / 2);
}
if(style.fillStyle){
c.fillText(text, width / 2, tCan.height/ 2);
}
// prep the particles
particles.empty();
// get the pixel data
var data = c.getImageData(0,0,width,tCan.height).data;
var x,y,ind,rgb,a;
// find pixels with alpha > 128
for(y = 0; y < tCan.height; y += 1){
for(x = 0; x < width; x += 1){
ind = (y * width + x) << 2; // << 2 is equiv to * 4
if(data[ind + 3] > 128){ // is alpha above half
rgb = `#${hex(data[ind ++])}${hex(data[ind ++])}${hex(data[ind ++])}`;
// add the particle
particles.add(Vec(x, y), Vec(x, y), rgb);
}
}
}
// scale the particles to fit bounding box
var scale = Math.min(fit.width / width, fit.height / tCan.height);
particles.each(p=>{
p.home.x = ((fit.left + fit.width) / 2) + (p.home.x - (width / 2)) * scale;
p.home.y = ((fit.top + fit.height) / 2) + (p.home.y - (tCan.height / 2)) * scale;
})
.findCenter() // get center used to move particles on and off of screen
.moveOffscreen() // moves particles off the screen
.moveIn(); // set the particles to move into view.
}
// vector object a quick copy from other code.
function Vec(x,y){ // because I dont like typing in new
return new _Vec(x,y);
}
function _Vec(x = 0,y = 0){
this.x = x;
this.y = y;
return this;
}
_Vec.prototype = {
setAs(vec){
this.x = vec.x;
this.y = vec.y;
},
toString(){
return `vec : { x : ${this.x}, y : ${this.y} );`
}
}
// basic particle
const particle = {
pos : null,
delta : null,
home : null,
col : "black",
}
// array of particles
const particles = {
items : [], // actual array of particles
mouseFX : { // mouse FX
power : 20,
dist : 100,
curve : 3, // polynomial power
on : true,
},
fx : {
speed : 0.4,
drag : 0.15,
size : 4,
jiggle : 8,
},
// direction 1 move in -1 move out
direction : 1,
moveOut(){this.direction = -1; return this},
moveIn(){this.direction = 1; return this},
length : 0, // Dont touch this from outside particles.
each(callback){ // custom iteration
for(var i = 0; i < this.length; i++){
callback(this.items[i],i);
}
return this;
},
empty(){ // empty but dont dereference
this.length = 0;
return this;
},
deRef(){ // call to clear memory
this.items.length = 0;
this.length = 0;
},
add(pos,home,col){ // adds a particle
var p;
if(this.length < this.items.length){
p = this.items[this.length++];
// p.pos.setAs(pos);
p.home.setAs(home);
p.delta.x = 0;
p.delta.y = 0;
p.col = col;
}else{
this.items.push(
Object.assign(
{},
particle,
{
pos,
home,
col,
delta : Vec()
}
)
);
this.length = this.items.length
}
return this;
},
draw(){ // draws all
var p, size, sizeh;
sizeh = (size = this.fx.size) / 2;
for(var i = 0; i < this.length; i++){
p = this.items[i];
ctx.fillStyle = p.col;
ctx.fillRect(p.pos.x - sizeh, p.pos.y - sizeh, size, size);
}
},
update(){ // update all particles
var p,x,y,d;
var mP = this.mouseFX.power;
var mD = this.mouseFX.dist;
var mC = this.mouseFX.curve;
var fxJ = this.fx.jiggle;
var fxD = this.fx.drag;
var fxS = this.fx.speed;
for(var i = 0; i < this.length; i++){
p = this.items[i];
p.delta.x += (p.home.x - p.pos.x ) * fxS + (Math.random() - 0.5) * fxJ;
p.delta.y += (p.home.y - p.pos.y ) * fxS + (Math.random() - 0.5) * fxJ;
p.delta.x *= fxD;
p.delta.y *= fxD;
p.pos.x += p.delta.x * this.direction;
p.pos.y += p.delta.y * this.direction;
if(this.mouseFX.on){
x = p.pos.x - mouse.x;
y = p.pos.y - mouse.y;
d = Math.sqrt(x * x + y * y);
if(d < mD){
x /= d;
y /= d;
d /= mD;
d = (1-Math.pow(d,mC)) * mP;
p.pos.x += x * d;
p.pos.y += y * d;
}
}
}
return this;
},
findCenter(){ // find the center of particles maybe could do without
var x,y;
y = x = 0;
this.each(p => {
x += p.home.x;
y += p.home.y;
});
this.center = Vec(x / this.length, y / this.length);
return this;
},
moveOffscreen(){ // move start pos offscreen
var dist,x,y;
dist = Math.sqrt(this.center.x * this.center.x + this.center.y * this.center.y);
this.each(p => {
var d;
x = p.home.x - this.center.x;
y = p.home.y - this.center.y;
d = Math.max(0.0001,Math.sqrt(x * x + y * y)); // max to make sure no zeros
p.pos.x = p.home.x + (x / d) * dist;
p.pos.y = p.home.y + (y / d) * dist;
});
return this;
},
}
function onResize(){ // called from boilerplate
if(!started){
startIt();
}
}
/** SimpleFullCanvasMouse.js begin **/
// the following globals are available
// w, h, cw, ch, width height centerWidth centerHeight of canvas
// canvas, ctx, mouse, globalTime
//MAIN animation loop
function display() { // call once per frame
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
if(tCan){
// ctx.drawImage(tCan,0,0);
}
particles.update();
particles.draw();
}
/******************************************************************************
The code from here down is generic full page mouse and canvas boiler plate
code. As I do many examples which all require the same mouse and canvas
functionality I have created this code to keep a consistent interface. The
Code may or may not be part of the answer.
This code may or may not have ES6 only sections so will require a transpiler
such as babel.js to run on legacy browsers.
*****************************************************************************/
// V2.0 ES6 version for Stackoverflow and Groover QuickRun
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0;
// You can declare onResize (Note the capital R) as a callback that is also
// called once at start up. Warning on first call canvas may not be at full
// size.
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var resizeTimeoutHandle;
var firstRun = true;
function createCanvas () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
function resizeCanvas () {
if (canvas === undefined) { canvas = createCanvas() }
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals() }
if (typeof onResize === "function") {
clearTimeout(resizeTimeoutHandle);
if (firstRun) { onResize() }
else { resizeTimeoutHandle = setTimeout(onResize, RESIZE_DEBOUNCE_TIME) }
firstRun = false;
}
}
function setGlobals () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) { e.preventDefault() }
var m; // alias for mouse
var mouse = {
x : 0, y : 0, w : 0, // mouse position and wheel
alt : false, shift : false, ctrl : false, // mouse modifiers
buttonRaw : 0,
over : false, // true if mouse over the element
buttonOnMasks : [0b1, 0b10, 0b100], // mouse button on masks
buttonOffMasks : [0b110, 0b101, 0b011], // mouse button off masks
active : false,
bounds : null,
eventNames : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(","),
event(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left - scrollX;
m.y = e.pageY - m.bounds.top - scrollY;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.buttonOnMasks[e.which - 1] }
else if (t === "mouseup") { m.buttonRaw &= m.buttonOffMasks[e.which - 1] }
else if (t === "mouseout") { m.over = false }
else if (t === "mouseover") { m.over = true }
else if (t === "mousewheel") {m.w = e.wheelDelta }
else if (t === "DOMMouseScroll") { m.w = -e.detail }
if (m.callbacks) { m.callbacks.forEach(c => c(e)) }
if ((m.buttonRaw & 2) && m.crashRecover !== null) {
if (typeof m.crashRecover === "function") { setTimeout(m.crashRecover, 0) }
}
e.preventDefault();
},
addCallback(callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) { m.callbacks = [callback] }
else { m.callbacks.push(callback) }
}
},
start(element) {
if (m.element !== undefined) { m.remove() }
m.element = element === undefined ? document : element;
m.eventNames.forEach(name => document.addEventListener(name, mouse.event) );
document.addEventListener("contextmenu", preventDefault, false);
m.active = true;
},
remove() {
if (m.element !== undefined) {
m.eventNames.forEach(name => document.removeEventListener(name, mouse.event) );
document.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
}
m = mouse;
return mouse;
})();
function done() { // Clean up. Used where the IDE is on the same page.
window.removeEventListener("resize", resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = undefined;
}
function update(timer) { // Main update loop
if(ctx === undefined){ return }
globalTime = timer;
display(); // call demo code
requestAnimationFrame(update);
}
setTimeout(function(){
canvas = createCanvas();
mouse.start(canvas, true);
resizeCanvas();
if(typeof Groover !== "undefined" && Groover.ide){ mouse.crashRecover = done }; // Requires Ace.js and GrooverDev.js. Prevents
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
/** SimpleFullCanvasMouse.js end **/
/** CreateImage.js begin **/
// creates a blank image with 2d context
function createImage(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}
/** CreateImage.js end **/
canvas {
background : black;
}
well after all the attempts like hell I finally got my answer thanks to moƔois answer to my post using particle slider. here is the code. Thanks everyone for your help :)

How to create Blur Eraser and Blur Drawing effect in JQuery or Javascript?

I want to make blur erasing and blur drawing effect in JQuery or Javascript. I have written the code for erasing the blurred image when I hover it and to reveal the unblurred image. Here's the screenshot.
I am able to erase the blur image when I hover over the image, but I couldn't redraw the blurred image at the same position, may be like after 500ms. How do I redraw the blur again over where I hovered with my mouse?
Here's the code for reference:
<!DOCTYPE html>
<html>
<head>
<title>Blur Testing</title>
<meta name="content-type" content="text/html; charset=UTF-8">
<style>
body {
margin: 0;
}
#item {
background: url("http://i66.tinypic.com/2z6uq9f.jpg");
background-size: cover;
background-position: center;
}
</style>
<script src="https://code.jquery.com/jquery-1.12.4.min.js" type="text/javascript"></script>
</head>
<body>
<canvas id="item"></canvas>
<script>
var canvas = document.getElementById('item');
var ctx = canvas.getContext('2d'),
img = new Image,
radius = 30;
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
$('#item').css({
"-webkit-filter": "blur(0px)",
"filter": "blur(0px)"
});
$(img).on('load', function () {
$('#item').mouseover(function (e) {
erase(getXY(e));
}).mousemove(function (e) {
erase(getXY(e));
//setTimeout(redraw(getXY(e)), 400);
});
ctx.drawImage(img, 0, 0);
ctx.globalCompositeOperation = 'destination-out';
});
img.src = 'http://i64.tinypic.com/14mt7yx.jpg';
img.width = window.width;
img.height = window.height;
function getXY(e) {
var r = $('#item')[0].getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top};
}
// function redraw(pos) {
// ctx.globalCompositeOperation = 'source-in';
// ctx.beginPath();
// ctx.arc(pos.x, pos.y, radius, 0, 2 * Math.PI);
// ctx.closePath();
// ctx.fill();
// };
function erase(pos) {
ctx.globalCompositeOperation = 'destination-out';
ctx.beginPath();
ctx.arc(pos.x, pos.y, radius, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
}
</script>
</body>
</html>
The following is a quick modification on Alpha Mask Filter question. I have simply reversed the blur and sharp images. See the linked question for details.
The extra bit to fade out the blur mask
blurMaskFadeCounter += 1;
if((blurMaskFadeCounter % blurMaskFadeRate) === 0){
maskImage.ctx.globalCompositeOperation = "destination-out";
maskImage.ctx.fillStyle = "#000";
maskImage.ctx.globalAlpha = 0.1;
maskImage.ctx.fillRect(0,0,maskImage.width,maskImage.height);
maskImage.ctx.globalAlpha = 1;
maskImage.ctx.globalCompositeOperation = "source-over";
}
Simply draws over the mask with destination-out composition and alpha set low. It is timed to every so many frames to slow it down. If you set alpha below 0.1 you get some pixels that wont completely clear so the frame skipping give a slower response
var imageLoadedCount = 0;
var error = false;
var maskImage;
var flowerImage;
var flowerImageBlur;
/** ImageTools.js begin **/
var imageTools = (function () {
var tools = {
canvas : function (width, height) { // create a blank image (canvas)
var c = document.createElement("canvas");
c.width = width;
c.height = height;
return c;
},
createImage : function (width, height) {
var image = this.canvas(width, height);
image.ctx = image.getContext("2d");
return image;
},
loadImage : function (url, callback) {
var image = new Image();
image.src = url;
image.addEventListener('load', callback);
image.addEventListener('error', callback);
return image;
}
};
return tools;
})();
var mouse;
var demo = function(){
/** fullScreenCanvas.js begin **/
var canvas = (function(){
var canvas = document.getElementById("canv");
if(canvas !== null){
document.body.removeChild(canvas);
}
// creates a blank image with 2d context
canvas = document.createElement("canvas");
canvas.id = "canv";
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
canvas.style.zIndex = 1000;
canvas.ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
return canvas;
})();
var ctx = canvas.ctx;
/** fullScreenCanvas.js end **/
/** MouseFull.js begin **/
if(typeof mouse !== "undefined"){ // if the mouse exists
if( mouse.removeMouse !== undefined){
mouse.removeMouse(); // remove previouse events
}
}else{
var mouse;
}
var canvasMouseCallBack = undefined; // if needed
mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false,
interfaceId : 0, buttonLastRaw : 0, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
getInterfaceId : function () { return this.interfaceId++; }, // For UI functions
startMouse:undefined,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
function mouseMove(e) {
var t = e.type, m = mouse;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === undefined) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey;m.shift = e.shiftKey;m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1];
} else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") { m.buttonRaw = 0; m.over = false;
} else if (t === "mouseover") { m.over = true;
} else if (t === "mousewheel") { m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") { m.w = -e.detail;}
if (canvasMouseCallBack) { canvasMouseCallBack(mouse); }
e.preventDefault();
}
function startMouse(element){
if(element === undefined){
element = document;
}
mouse.element = element;
mouse.mouseEvents.forEach(
function(n){
element.addEventListener(n, mouseMove);
}
);
element.addEventListener("contextmenu", function (e) {e.preventDefault();}, false);
}
mouse.removeMouse = function(){
if(mouse.element !== undefined){
mouse.mouseEvents.forEach(
function(n){
mouse.element.removeEventListener(n, mouseMove);
}
);
canvasMouseCallBack = undefined;
}
}
mouse.mouseStart = startMouse;
return mouse;
})();
if(typeof canvas !== "undefined"){
mouse.mouseStart(canvas);
}else{
mouse.mouseStart();
}
/** MouseFull.js end **/
// load the images and create the mask
if(imageLoadedCount === 0){
imageLoadedCount = 0;
error = false;
maskImage;
flowerImage = imageTools.loadImage("http://www.createjs.com/demos/_assets/art/flowers.jpg", function (event) {
if (event.type === "load") {
imageLoadedCount += 1;
} else {
error = true;
}
})
flowerImageBlur = imageTools.loadImage("http://i.stack.imgur.com/3S5m8.jpg", function () {
if (event.type === "load") {
maskImage = imageTools.createImage(this.width, this.height);
imageLoadedCount += 1;
} else {
error = true;
}
})
}
// set up the canvas
var w = canvas.width;
var h = canvas.height;
var cw = w / 2;
var ch = h / 2;
// calculate time to download image using the MS algorithum. As this code is a highly gaurded secret I have obsficated it for your personal safty.
var calculateTimeToGo= (function(){var b="# SecondQMinuteQHourQDayQWeekQMonthQMomentQTick#.,Some time soon,Maybe Tomorrow.".replace(/Q/g,"#.,# ").split(","),r=Math.random,f=Math.floor,lc=0,pc=0,lt=0,lp=0;var cttg=function(a){if(lc===0){lc=100+r(r()*60);lt=f(r()*40);if(pc===0||r()<(lp/b.length)-0.2){lp=f(r()*b.length);pc=1+f(r()*10)}else{pc-=1}}else{lc-=1}a=lt;if(lp===0){a=lt;if(r()<0.01){lt-=1}}var s=b[lp].replace("#",a);if(a===1){s=s.replace("#","")}else{s=s.replace("#","s")}return s};return cttg})();
// draws circle with gradient
function drawCircle(ctx, x, y, r) {
var gr = ctx.createRadialGradient(x, y, 0, x, y, r)
gr.addColorStop(1, "rgba(0,0,0,0)")
gr.addColorStop(0.5, "rgba(0,0,0,0.08)")
gr.addColorStop(0, "rgba(0,0,0,0.1)")
ctx.fillStyle = gr;
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
ctx.fill();
}
// draw text
function drawText(ctx, text, size, x, y, c) {
ctx.fillStyle = c;
ctx.strokeStyle = "black";
ctx.lineWidth = 5;
ctx.lineJoin = "round";
ctx.font = size + "px Arial Black";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
if (c !== "black") {
ctx.strokeText(text, x, y + 1);
}
ctx.fillText(text, x, y);
}
// draw the image to fit the current canvas size
function drawImageCentered(ctx, image, x, y) {
var scale = Math.min(w / image.width, h / image.height);
ctx.setTransform(scale, 0, 0, scale, cw, ch);
ctx.drawImage(image, -image.width / 2, -image.height / 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
}
// how often to fade blur mask
var blurMaskFadeRate =8; // number of frames between fading mask out
var blurMaskFadeCounter = 0;
// points for filling gaps between mouse moves.
var lastMX,lastMY;
// update function will try 60fps but setting will slow this down.
function update(time){
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore transform
ctx.clearRect(0, 0, w, h); // clear rhe canvas
// have the images loaded???
if (imageLoadedCount === 2) {
// draw the unblured image that will appear at the top
ctx.globalCompositeOperation = "source-over";
drawImageCentered(ctx, flowerImageBlur, cw, ch);
drawText(ctx, "Move mouse over image to unblur.", 20 + Math.sin(time / 100), cw, ch - 30, "White");
// Mask out the parts when the mask image has pixels
ctx.globalCompositeOperation = "destination-out";
drawImageCentered(ctx, maskImage, cw, ch);
// draw the blured image only where the destination has been masked
ctx.globalCompositeOperation = "destination-atop";
drawImageCentered(ctx, flowerImage, cw, ch);
blurMaskFadeCounter += 1;
if((blurMaskFadeCounter % blurMaskFadeRate) === 0){
maskImage.ctx.globalCompositeOperation = "destination-out";
maskImage.ctx.fillStyle = "#000";
maskImage.ctx.globalAlpha = 0.1;
maskImage.ctx.fillRect(0,0,maskImage.width,maskImage.height);
maskImage.ctx.globalAlpha = 1;
maskImage.ctx.globalCompositeOperation = "source-over";
}
// because image has been scaled need to get mouse coords on image
var scale = Math.min(w / flowerImage.width, h / flowerImage.height);
var x = (mouse.x - (cw - (maskImage.width / 2) * scale)) / scale;
var y = (mouse.y - (ch - (maskImage.height / 2) * scale)) / scale;
// draw circle on mask
drawCircle(maskImage.ctx, x, y, 60);
// if mouse is draging then draw some points between to fill the gaps
if (lastMX !== undefined) {
drawCircle(maskImage.ctx, ((x + lastMX) / 2 + x) / 2, ((y + lastMY) / 2 + y) / 2, 60);
drawCircle(maskImage.ctx, (x + lastMX) / 2, (y + lastMY) / 2, 60);
drawCircle(maskImage.ctx, ((x + lastMX) / 2 + lastMX) / 2, ((y + lastMY) / 2 + lastMY) / 2, 60);
}
// save las mouse pos on image
lastMX = x;
lastMY = y;
} else {
// Laoding images so please wait.
drawText(ctx, "Please wait.", 40 + Math.sin(time / 100), cw, ch - 30, "White");
drawText(ctx, "loading images... ", 12, cw, ch, "black")
drawText(ctx, "ETA " + calculateTimeToGo(time), 14, cw, ch + 20, "black")
}
// if not restart the request animation frame
if(!STOP){
requestAnimationFrame(update);
}else{
var can = document.getElementById("canv");
if(can !== null){
document.body.removeChild(can);
}
STOP = false;
}
}
update();
}
var STOP = false; // flag to tell demo app to stop
function resizeEvent() {
var waitForStopped = function () {
if (!STOP) { // wait for stop to return to false
demo();
return;
}
setTimeout(waitForStopped, 200);
}
STOP = true;
setTimeout(waitForStopped, 100);
}
window.addEventListener("resize", resizeEvent);
demo();

Drag and drop on a canvas HTML5

I am trying to implement a drag and drop on a canvas representing 3 disks.
I would like to change with mouse the position of each mass. My main problem is that I am constrained by the length of axe for each of these 3 spheres.
For the moment, I have implemented the following function when mouse is moving inside the canvas (value of indexMass indicates which mass is moved: 1, 2 or 3 and t1, t2, t3 represents respectively the angle of mass 1, 2, 3):
// Happens when the mouse is moving inside the canvas
function myMove(event) {
if (isDrag) {
var x = event.offsetX;
var y = event.offsetY;
if (indexMass == 1)
{ // Update theta1 value
t1 = t1 + 0.1*Math.atan(y/x);
}
else if (indexMass == 2)
{ // Update theta2 value
t2 = t2 + 0.1*Math.atan(y/x);
}
else if (indexMass == 3)
{ // Update theta3 value
t3 = t3 + 0.1*Math.atan(y/x);
}
// Update drawing
DrawPend(canvas);
}
}
As you can see, I did for each angle:
t = t + 0.1*Math.atan(y/x);
with:
var x = event.offsetX;
var y = event.offsetY;
But this effect is not very nice. Once the sphere is selected with mouse (on mouse click), I would like the cursor to be stuck with this sphere or the sphere to follow the "delta" of the mouse coordinates when I am not on sphere any more.
Update 1
#Blindman67: thanks for your help, your code snippet is pretty complex for me, I didn't understand it all. But I am on the right way.
I am starting by the first issue: make rotate the selected disk with mouse staying very closed to it or over it, when dragging.
For the moment, I have modified my function myMove (which is called when I have clicked down and move the mouse for dragging) like:
// Happens when the mouse is moving inside the canvas
function myMove(event) {
// If dragging
if (isDrag) {
// Compute dx and dy before calling DrawPend
var lastX = parseInt(event.offsetX - mx);
var lastY = parseInt(event.offsetY - my);
var dx = lastX - window['x'+indexMass];
var dy = lastY - window['y'+indexMass];
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);
// Update drawing
DrawPend(canvas);
// Highlight dragging disk
fillDisk(indexMass, 'pink');
}
}
where indexMass is the index of dragged disk and window['x'+indexMass] , window['y'+indexMass] are the current coordinates of the selected disk center.
After, I compute the dx, dy respectively from coordinates mouse clicked when starting drag (mx, my returned by getMousePos function) and mouse coordinates with moving.
Finally, I change the angle of disk by set, for global variable (theta of selected disk), i.e window['t'+indexMass]:
// Change angle when dragging
window['t'+indexMass] = Math.atan2(dy, dx);
I have took your part of code with Math.atan2.
But the result of this function doesn't make a good animation with mouse dragging, I would like to know where this could come from.
Right now, I would like to implement only the dragging without modifying the length of axis, I will see more later for this functionality.
Update 2
I keep going on to find a solution about the dragging of a selected mass with mouse.
For trying a synthesis of what I have done previously, I believe the following method is good but this dragging method is not working very well: the selected disk doesn't follow correctly the mouse and I don't know why.
In myMove function (function called when I start dragging), I decided to:
Compute the dx, dy between the mouse coordinates and the selected disk coordinates, i.e:
var dx = parseInt(event.offsetX - window['x'+indexMass]);
var dy = parseInt(event.offsetY - window['y'+indexMass]);
indexMass represents the index of the selected disk.
Increment the position of selected disk (stored in temporary variables tmpX, tmpY) by dx, dy.
Compute the new angle theta (identified in code by global variable window['t'+indexMass]
Compute the new positions of selected disk with this new value of theta, i.e for example with disk1 (indexMass=1 and theta = t1):
x1= x0 +l1 * sin(t1)
y1= y0 +l1 * sin(t1)
I want to draw readers' attention to the fact that I want dragging with mouse not to modify the lengths of axes with mouse, this is a constraint.
Here's the entire myMove function (called when drag is starting) :
// Happens when the mouse is moving inside the canvas
function myMove(event) {
// If dragging
if (isDrag) {
console.log('offsetX', event.offsetX);
console.log('offsetY', event.offsetY);
var dx = parseInt(event.offsetX - window['x'+indexMass]);
var dy = parseInt(event.offsetY - window['y'+indexMass]);
console.log('dx', dx);
console.log('dy', dy);
// Temp variables
var tmpX = window['x'+indexMass];
var tmpY = window['y'+indexMass];
// Increment temp positions
tmpX += dx;
tmpY += dy;
// Compute new angle for indexMass
window['t'+indexMass] = Math.atan2(tmpX, tmpY);
console.log('printf', window['t'+indexMass]);
// Compute new positions of disks
dragComputePositions();
// Update drawing
DrawPend(canvas);
// Highlight dragging disk
fillDisk(indexMass, 'pink');
}
}
You can not move the OS mouse position. You can hide the mouse canvas.style.cursor = "none"; and then draw a mouse on the canvas your self but it will lag behind by one frame because when you get the mouse coordinates the OS has already placed the mouse at that position, and if you use requestAnimationFrame (RAF) the next presentation of the canvas will be at the next display refresh interval. If you don't use RAF you may or may not present the canvas on the current display refresh, but you will get occasional flicker and shearing.
To solve the problem (which is subjective) draw a line from the rotation point through the ball to the mouse position this will at least give the user some feedback as to what is happening.
I would also add some handles to the balls so you could change the mass (volume of sphere * density) and the length of axis.. The resize cursors are a problem as the will not match the direction of required movement when the angles have changes. You would need to find one closest to the correct angle or render a cursor to a canvas and use that.
Example code shows what I mean. (does not include sim) Move mouse over balls to move, when over you will also see two circles appear to change distance and radius (mass)
/*-------------------------------------------------------------------------------------
answer code
---------------------------------------------------------------------------------------*/
var balls = [];
var startX,startY;
var mouseOverBallIndex = -1;
var mouseOverDist = false;
var mouseOverMass = false;
const DRAG_CURSOR = "move";
const MASS_CURSOR = "ew-resize";
const DIST_CURSOR = "ns-resize";
var dragging = false;
var dragStartX = 0;
var dragStartY = 0;
function addBall(dist,radius){
balls.push({
dist : dist,
radius : Math.max(10,radius),
angle : -Math.PI / 2,
x : 0,
y : 0,
mass : (4/3) * radius * radius * radius * Math.PI,
});
}
function drawBalls(){
var i = 0;
var len = balls.length;
var x,y,dist,b,minDist,index,cursor;
ctx.lineWidth = 2;
ctx.strokeStyle = "black";
ctx.fillStyle = "blue"
ctx.beginPath();
x = startX;
y = startY;
ctx.moveTo(x, y)
for(; i < len; i += 1){
b = balls[i];
x += Math.cos(b.angle) * b.dist;
y += Math.sin(b.angle) * b.dist;
ctx.lineTo(x, y);
b.x = x;
b.y = y;
}
ctx.stroke();
minDist = Infinity;
index = -1;
for(i = 0; i < len; i += 1){
b = balls[i];
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
if(!dragging){
x = b.x - mouse.x;
y = b.y - mouse.y;
dist = Math.sqrt(x * x + y * y);
if(dist < b.radius + 5 && dist < minDist){
minDist = dist;
index = i;
}
}
}
if(!dragging){
mouseOverBallIndex = index;
if(index !== -1){
cursor = DRAG_CURSOR;
b = balls[index];
ctx.fillStyle = "Red"
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
dx = b.x - Math.cos(b.angle) * b.radius;
dy = b.y - Math.sin(b.angle) * b.radius;
x = dx - mouse.x;
y = dy - mouse.y;
dist = Math.sqrt(x * x + y * y);
ctx.beginPath();
if(dist < 6){
ctx.strokeStyle = "Yellow"
mouseOverDist = true;
ctx.arc(dx, dy, 12, 0, Math.PI * 2);
cursor = DIST_CURSOR;
}else{
ctx.strokeStyle = "black"
mouseOverDist = false;
ctx.arc(dx, dy, 5, 0, Math.PI * 2);
}
ctx.stroke();MASS_CURSOR
dx = b.x - Math.cos(b.angle + Math.PI/2) * b.radius;
dy = b.y - Math.sin(b.angle + Math.PI/2) * b.radius;
x = dx - mouse.x;
y = dy - mouse.y;
dist = Math.sqrt(x * x + y * y);
ctx.beginPath();
if(dist < 6){
ctx.strokeStyle = "Yellow"
mouseOverMass = true;
ctx.arc(dx, dy, 12, 0, Math.PI * 2);
cursor = MASS_CURSOR;
}else{
ctx.strokeStyle = "black"
mouseOverMass = false;
ctx.arc(dx, dy, 5, 0, Math.PI * 2);
}
ctx.stroke();
canvas.style.cursor = cursor;
}else{
canvas.style.cursor = "default";
}
}else{
b = balls[mouseOverBallIndex];
ctx.fillStyle = "Yellow"
ctx.beginPath();
ctx.arc(b.x, b.y, b.radius, 0, Math.PI * 2);
ctx.fill();
}
}
function display(){ // put code in here
var x,y,b
if(balls.length === 0){
startX = canvas.width/2;
startY = canvas.height/2;
addBall((startY * 0.8) * (1/4), startY * 0.04);
addBall((startY * 0.8) * (1/3), startY * 0.04);
addBall((startY * 0.8) * (1/2), startY * 0.04);
}
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
if((mouse.buttonRaw & 1) && mouseOverBallIndex > -1){
b = balls[mouseOverBallIndex];
if(dragging === false){
dragging = true;
dragStartX = balls[mouseOverBallIndex].x;
dragStartY = balls[mouseOverBallIndex].y;
}else{
b = balls[mouseOverBallIndex];
if(mouseOverBallIndex === 0){
x = startX;
y = startY;
}else{
x = balls[mouseOverBallIndex-1].x
y = balls[mouseOverBallIndex-1].y
}
if(mouseOverDist){
var dist = Math.sqrt(Math.pow(x-mouse.x,2)+Math.pow(y-mouse.y,2));
b.dist = dist + b.radius;
}else
if(mouseOverMass){
var dist = Math.sqrt(Math.pow(dragStartX-mouse.x,2)+Math.pow(dragStartY-mouse.y,2));
b.radius = Math.max(10,dist);
b.mass = dist * dist * dist * (4/3) * Math.PI;
}else{
b.angle = Math.atan2(mouse.y - y, mouse.x - x);
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "grey";
ctx.moveTo(x,y);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
}
}
}else if(dragging){
dragging = false;
}
drawBalls();
}
/*-------------------------------------------------------------------------------------
answer code END
---------------------------------------------------------------------------------------*/
/** SimpleFullCanvasMouse.js begin **/
const CANVAS_ELEMENT_ID = "canv";
const U = undefined;
var w, h, cw, ch; // short cut vars
var canvas, ctx, mouse;
var globalTime = 0;
var createCanvas, resizeCanvas, setGlobals;
var L = typeof log === "function" ? log : function(d){ console.log(d); }
createCanvas = function () {
var c,cs;
cs = (c = document.createElement("canvas")).style;
c.id = CANVAS_ELEMENT_ID;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === U) { canvas = createCanvas(); }
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") { setGlobals(); }
}
setGlobals = function(){ cw = (w = canvas.width) / 2; ch = (h = canvas.height) / 2; balls.length = 0; }
mouse = (function(){
function preventDefault(e) { e.preventDefault(); }
var mouse = {
x : 0, y : 0, w : 0, alt : false, shift : false, ctrl : false, buttonRaw : 0,
over : false, // mouse is over the element
bm : [1, 2, 4, 6, 5, 3], // masks for setting and clearing button raw bits;
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.x = e.offsetX; m.y = e.offsetY;
if (m.x === U) { m.x = e.clientX; m.y = e.clientY; }
m.alt = e.altKey; m.shift = e.shiftKey; m.ctrl = e.ctrlKey;
if (t === "mousedown") { m.buttonRaw |= m.bm[e.which-1]; }
else if (t === "mouseup") { m.buttonRaw &= m.bm[e.which + 2]; }
else if (t === "mouseout") { m.buttonRaw = 0; m.over = false; }
else if (t === "mouseover") { m.over = true; }
else if (t === "mousewheel") { m.w = e.wheelDelta; }
else if (t === "DOMMouseScroll") { m.w = -e.detail; }
if (m.callbacks) { m.callbacks.forEach(c => c(e)); }
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === U) { m.callbacks = [callback]; }
else { m.callbacks.push(callback); }
} else { throw new TypeError("mouse.addCallback argument must be a function"); }
}
m.start = function (element, blockContextMenu) {
if (m.element !== U) { m.removeMouse(); }
m.element = element === U ? document : element;
m.blockContextMenu = blockContextMenu === U ? false : blockContextMenu;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
if (m.blockContextMenu === true) { m.element.addEventListener("contextmenu", preventDefault, false); }
}
m.remove = function () {
if (m.element !== U) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
if (m.contextMenuBlocked === true) { m.element.removeEventListener("contextmenu", preventDefault);}
m.element = m.callbacks = m.contextMenuBlocked = U;
}
}
return mouse;
})();
var done = function(){
window.removeEventListener("resize",resizeCanvas)
mouse.remove();
document.body.removeChild(canvas);
canvas = ctx = mouse = U;
L("All done!")
}
resizeCanvas(); // create and size canvas
mouse.start(canvas,true); // start mouse on canvas and block context menu
window.addEventListener("resize",resizeCanvas); // add resize event
function update(timer){ // Main update loop
globalTime = timer;
display(); // call demo code
// continue until mouse right down
if (!(mouse.buttonRaw & 2)) { requestAnimationFrame(update); } else { done(); }
}
requestAnimationFrame(update);
/** SimpleFullCanvasMouse.js end **/
(Posted a solution from the question author to move it to the answer space).
Problem solved! I forgot to take into account the position of "indexMass-1" disk to compute the new angle with Math.atan2 function.

Create a div like element that has overflow set to auto using HTML Canvas

The title might be misleading but that is the best I could come up with for a summary of my question.
Anyways, I need to figure out how to make a list, or a container, in this case a plain rectangle that contains a list of items, which can be dragged up and down in order to reveal other items in the container. In a way it would resemble a constrained div with a slider bar, but without the slider.
Now, I have an idea on using KonvaJS, former KineticJS to put all the items in the container in a group, and make the group draggable in certain directions, etc.
However the catch is that the sliding of the elements top or down should not only be on drag, but on flick also. So if you kind of flick your finger/mouse upwards the list would keep sliding by, until the end, where the speed would vary based on the flick intensity. If determining the flick intensity or speed is too complicated, then just any type of flick would need to slide the whole list to the bottom, or top.
So this should kind of resemble the standard vertical slide widgets you have on your android or ios. Now do you have any ideas on how I can proceed with this, or how would you go about this. Any ideas are welcome.
Working demo: http://jsbin.com/gefuvu/edit?js,output
Usual drag and drop is already supported by draggable property. For limit drag&drop to vertical scrolling I am using this simple dragBound:
const group = new Konva.Group({
draggable: true,
dragBoundFunc: (pos) => {
const minY = -group.getClientRect().height + stage.height();
const maxY = 0;
const y = Math.max(Math.min(pos.y, maxY), minY);
return {y, x: 0}
}
});
"Flick" implementation:
// setup flick
let lastY = null;
let dY = 0;
group.on('dragstart', () => {
lastY = group.y();
dy = 0;
});
group.on('dragmove', () => {
dy = lastY - group.y();
lastY = group.y();
});
group.on('dragend', () => {
// if last move is far way it means user move pointer very fast
// for this case we need to automatically "scroll" group
if (dy > 5) {
group.to({
y: -group.getClientRect().height + stage.height()
});
}
if (dy < -5) {
group.to({
y: 0
});
}
});
I guess that when you talk about "flick" you actually mean "scroll".
Edit : Missed the point of the question, also missed the [konvajs] tag. But here is a way to do it without any library, hoping it may help someone coming this way.
The simplest idea is to make two objects, a container and a content, each one with a canvas.
On mouse's wheel event, update the content position, then redraw its canvas to the container's one or if you need to handle drag, listen to the mousemove event, set a dragging flag to true, that you remove on mouseup. On mousemove update the position after you calculated the moving speed by checking the last event's timestamp and the new one's. Then on mouseup, start an animation that will decrease the speed of your movement :
// our container object
var container = {
width: window.innerWidth - 2,
height: window.innerHeight - 2,
top: 0,
left: 0,
canvas: document.getElementById('container'),
isOver: function(x, y) {
return (x >= this.left && x <= this.left + this.width &&
y >= this.top && y <= this.top + this.height);
},
};
// our content object
var content = {
width: container.width * 2,
height: container.height * 2,
top: 0,
left: 0,
background: 'rgba(0,255,0,.5)',
canvas: document.createElement('canvas'),
// set an init function to draw the texts
init: function() {
var ctx = this.ctx;
ctx.font = '20px sans-serif';
ctx.textBaseline = 'top';
ctx.fillText('Hello World', 0, 0);
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillText('Middle world', this.width / 2, this.height / 2);
ctx.textBaseline = 'bottom';
ctx.textAlign = 'left';
var textLength = ctx.measureText('Bye World').width;
ctx.fillText('Bye World', this.canvas.width - textLength, this.canvas.height);
ctx.fillStyle = this.background;
ctx.fillRect(0, 0, this.width, this.height);
},
};
// init the objects
var init = function(obj) {
var c = obj.canvas;
obj.ctx = c.getContext('2d');
c.width = obj.width;
c.height = obj.height;
if (obj.init) {
obj.init();
}
}
// our drawing function
var draw = function() {
container.ctx.clearRect(0, 0, container.width, container.height);
container.ctx.drawImage(content.canvas, content.left, content.top);
};
// update the content position
container.update = function(x, y) {
// if the content is smaller, we don't need to scroll
if (content.width > container.width) {
var maxX = Math.max(container.width, content.width);
var minX = Math.min(container.width, content.width);
content.left -= x;
// if we are at one end
if (content.left < minX - maxX) {
content.left = minX - maxX;
} // or another
else if (content.left > 0) {
content.left = 0;
}
}
if (content.height > container.height) {
var maxY = Math.max(container.height, content.height);
var minY = Math.min(container.height, content.height);
content.top -= y;
if (content.top < minY - maxY) {
content.top = minY - maxY;
} else if (content.top > 0) {
content.top = 0;
}
}
};
var drag = {
friction: .1,
sensibility: 18,
minSpeed: .01,
};
var mouseMove_Handler = function(e) {
// we're not dragging anything, stop here
if (!drag.dragged) {
return;
}
var rect = this.getBoundingClientRect();
var posX = e.clientX - rect.left;
var posY = e.clientY - rect.top;
// how long did it take since last event
var deltaTime = (e.timeStamp - drag.lastDragTime) / drag.sensibility;
// our moving speed
var deltaX = (drag.lastDragX - posX) / deltaTime;
var deltaY = (drag.lastDragY - posY) / deltaTime;
// update the drag object
drag.lastDragX = posX;
drag.lastDragY = posY;
drag.lastDeltaX = deltaX;
drag.lastDeltaY = deltaY;
drag.lastDragTime = e.timeStamp;
// update the container obj
drag.dragged.update(deltaX, deltaY);
// redraw
draw();
};
var mouseDown_Handler = function(e) {
// if we are sliding, stop it
if (drag.sliding) {
cancelAnimationFrame(drag.sliding);
drag.sliding = null;
}
var rect = this.getBoundingClientRect();
var posX = e.clientX - rect.left;
var posY = e.clientY - rect.top;
// first check that the event occurred on top of our container object
// we could loop through multiple ones
if (container.isOver(posX, posY)) {
// init our drag object
drag.dragged = container;
drag.lastDragX = posX;
drag.lastDragY = posY;
drag.lastDragTime = e.timeStamp;
}
};
var mouseUp_Handler = function(e) {
// store a ref of which object we were moving
var container = drag.dragged;
// we're not dragging anymore
drag.dragged = false;
var slide = function() {
// decrease the speed
drag.lastDeltaX /= 1 + drag.friction;
drag.lastDeltaY /= 1 + drag.friction;
// check that we are still out of our minimum speed
if (drag.lastDeltaX > drag.minSpeed || drag.lastDeltaY > drag.minSpeed ||
drag.lastDeltaX < -drag.minSpeed || drag.lastDeltaY < -drag.minSpeed) {
// store a reference of the animation
drag.sliding = requestAnimationFrame(slide);
} else {
drag.sliding = null;
drag.lastDeltaX = drag.lastDeltaY = 0;
}
container.update(drag.lastDeltaX, drag.lastDeltaY);
draw();
};
slide();
};
// add the wheel listener, for a polyfill check the MDN page :
// https://developer.mozilla.org/en-US/docs/Web/Events/wheel#Listening_to_this_event_across_browser
var mouseWheel_Handler = function(e) {
// get the position of our canvas element
var rect = this.getBoundingClientRect();
var posX = e.clientX - rect.left;
var posY = e.clientY - rect.top;
// first check that the event occurred on top of our container object
if (container.isOver(posX, posY)) {
// tell the browser we handle it
e.preventDefault();
e.stopPropagation();
// send the event's deltas
container.update(e.deltaX, e.deltaY);
// redraw
draw();
}
};
container.canvas.addEventListener('mousedown', mouseDown_Handler);
container.canvas.addEventListener('mousemove', mouseMove_Handler);
container.canvas.addEventListener('mouseup', mouseUp_Handler);
container.canvas.addEventListener('mouseleave', mouseUp_Handler);
container.canvas.addEventListener('wheel', mouseWheel_Handler);
// init the objects
init(container);
init(content);
// make a first draw
draw();
// Snippet only preventions \\
// avoid the outer window to scroll
window.onscroll = function(e) {
e.preventDefault();
e.stopPropagation()
};
// if you go in full page view
window.onresize = function() {
container.width = window.innerWidth;
container.height = window.innerHeight;
content.width = container.width * 2;
content.height = container.height * 2;
init(container);
init(content);
draw();
};
body,html,canvas {
margin: 0;
display: block
}
canvas {
border: 1px solid;
}
<canvas id="container"></canvas>

PhoneGap: Canvas Rendering on iPhone

I have a canvas in a PhoneGap app for iPhone and I'm building a lock pattern into it, using something very similar to < How I can create a pattern login like Android in HTML5? >. I rewrote parts to keep jQuery off the app, and also the circles and line are much less detailed.
I am doing desktop testing in Chrome, and also checked in Safari, this code works great and just as expected. My problem, however, is that nothing gets rendered on my iPhone 5 nor the iOS Simulator.
GLOBAL.Swiper = function( element , width , height ) {
var _self = this;
var theCanvas;
var code = '';
var stopDrawing = false;
var dragging=false;
var event = {
start : 'ontouchstart' in window ? 'touchstart' : 'mousedown',
move : 'ontouchstart' in window ? 'touchmove' : 'mousemove',
end : 'ontouchstart' in window ? 'touchend' : 'mouseup'
}
getCode = function() {
return code;
}
resetCode = function() {
code = '';
}
init = function( element , width , height) {
// Mark the Canvas, get its context, and set up the options
theCanvas = {
el: element,
ctx : element.getContext('2d'),
height : height || element.height,
width : width || element.width,
pad : 20 ,
circles : [],
selectedCircles : [],
startPoint : {x:0, y:0}
}
var rad = (theCanvas.width - theCanvas.pad * 4) / 3;
// Set up the circles
for ( var i=0;i<3;i++ ) {
for ( var j=0;j<3;j++ ) {
theCanvas.circles.push ( new Circle ({
x : j * ( theCanvas.pad + rad ) + theCanvas.pad + rad / 2,
y : i * ( theCanvas.pad + rad ) + theCanvas.pad + rad / 2
}, rad / 2) )
}
}
theCanvas.el.addEventListener (event.start , onStart );
theCanvas.el.addEventListener (event.move , onMove );
theCanvas.el.addEventListener (event.end , onEnd );
console.log ("Swiper Initialized");
drawCircles();
}
/**
* Circle
**/
Circle = function (center, radius, fill, stroke, hover, active) {
console.log ("NEW CIRCLE CREATED");
var center = {
x: center.x,
y: center.y
},
radius = radius,
fill = fill || '#482b5a',
hover = hover || '#663d80',
active = active || '#71b644',
stroke = stroke || '';
this.position = function(){
return {x:center.x, y:center.y};
}
this.draw = function(ctx) {
ctx.fillStyle = this.selected ? active : this.hovering ? hover : fill;
if (stroke) ctx.strokeStyle = stroke;
ctx.beginPath();
ctx.arc(center.x, center.y, radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
if (stroke) ctx.stroke();
};
this.isPointInPath = function(x, y) {
return Math.sqrt(Math.pow(center.x - x, 2) + Math.pow(center.y - y, 2)) <= radius;
};
this.hovering = false;
this.selected = false;
}
/**
* drawCircles
**/
drawCircles = function() {
var ctx = theCanvas.ctx;
ctx.clearRect(0,0,theCanvas.width,theCanvas.height);
for (var i = 0; i < theCanvas.circles.length; i++) {
theCanvas.circles[i].draw(ctx);
}
// Draw the lines connecting each circle
if (dragging && theCanvas.selectedCircles.length > 1) {
var pos = theCanvas.selectedCircles[0].circ.position();
ctx.beginPath();
ctx.lineWidth = 10;
ctx.strokeStyle = "#251";
ctx.moveTo(pos.x, pos.y);
for (var j = 1; j < theCanvas.selectedCircles.length; j++){
pos = theCanvas.selectedCircles[j].circ.position();
ctx.lineTo(pos.x,pos.y);
}
ctx.stroke();
ctx.closePath();
}
}
/**
* events - onStart, onMove, onEnd
**/
onStart = function() {
dragging = true;
drawCircles();
}
onMove = function(e) {
for (var i = 0; i < theCanvas.circles.length; i++) {
var cir = theCanvas.circles[i];
var pip = cir.isPointInPath(e.offsetX, e.offsetY);
cir.hovering = pip;
if (dragging && pip && !cir.selected) {
theCanvas.selectedCircles.push({circ:cir, index:i});
cir.selected = true;
}
drawCircles();
}
}
onEnd = function() {
dragging = false;
code = '';
while ( theCanvas.selectedCircles.length > 0 ) {
var singleCircle;
theCanvas.selectedCircles[0].circ.selected = false;
singleCircle = theCanvas.selectedCircles.shift();
code += (singleCircle.index + 1).toString();
}
alert (code);
// reset selection
for (var i = 0; i < theCanvas.selectedCircles.length; i++)
theCanvas.selectedCircles[i].circ.selected = false;
theCanvas.selectedCircles = [];
drawCircles();
}
if ( element ) init ( element , width , height);
return {
init : init,
draw : drawCircles,
getCode : getCode,
resetCode : resetCode,
c : theCanvas
}
}
As usual, any help will be appreciated.

Categories

Resources