jQuery connect with lines - javascript

How can I connect numbers with dotted lines in the below jsfiddle.
I want these lines to start from border of inner circle, either from jQuery or from css. I mean around border of inner circle start from inner border edge to number , ....... 1, ..... 2 to show guide lines.
http://jsfiddle.net/ineffablep/x03f61db/
function createFields() {
$('.field').remove();
var container = $('#container');
for(var i = 0; i < +$('input:text').val()+1; i++) {
$('<div/>', {
'class': 'field',
'text': i
}).appendTo(container);
}
}
function distributeFields() {
var fields = $('.field'), container = $('#container'),
width = center.width()*2, height = center.height()*2,
angle = 0, step = (2*Math.PI) / fields.length;
var radius = width/2;
var containerLength=$('input:text').val();
angle=step* (containerLength-1);
fields.each(function() {
var x = Math.round(width + radius * Math.cos(angle));
var y = Math.round(height + radius * Math.sin(angle));
$(this).css({
right: x + 'px',
top: y + 'px'
});
angle -= step;
});
}
var center = $('#center');
$(window).resize(function(height) {
$('#container').width($(window).height()*0.9)
$('#container').height($(window).height()*0.9)
var width = $('#container').width() * 0.4;
console.log("width",$('#container').width());
console.log("height",$('#container').height());
var radius = width/2;
width += 'px';
radius += 'px';
center.css({
width: width, height: width,
'-moz-border-radius' : radius,
'-webkit-border-radius' : radius,
'border-radius' : radius
});
createFields();
distributeFields();
// rest of your code for font-size setting etc..
});
$(window).resize();
$('input').change(function() {
createFields();
distributeFields();
});

I have achieved this with Canvas
context.fillStyle = '#00867F';
context.fill();
context.lineWidth = 1;
context.strokeStyle = '#F0EBF1';
context.stroke();
context.setLineDash([1,2]);
context.moveTo(lx, ly);
context.lineTo(nx, ny);

Related

Contain a rotated object inside another rotated object FabricJS

I have two objects a parent (red) and a child (blue). The parent object is fixed and can't be moved, only the child object is movable and the child is always bigger than the parent. In whatever way the child object is moved it should always be contained inside the child, which means we should never see the red rectangle.
Demo: https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-7nt7q
I know there are solutions to contain an object within the canvas or other object boundaries (ex. Move object within canvas boundary limit) which mainly force the top/right/bottom/left values to not exceed the parent values, but here we have the case of two rotated objects by the same degree.
I have a real-life scenario when a user uploads a photo to a frame container. The photo is normally always bigger than the frame container. The user can move the photo inside the frame, but he should not be allowed to create any empty spaces, the photo should always be contained inside the photo frame.
I would go with a pure canvas (no fabricjs), do it from scratch that way you understand well the problem you are facing, then if you need it that same logic should be easily portable to any library.
You have some rules:
The parent object is fixed and can't be moved,
The child object is movable.
The child is always bigger than the parent.
The child object is always constrained by the parent.
So my idea is to get all four corners, that way on the move we can use those coordinates to determine if it can be moved to the new location or not, the code should be easy to follow, but ask if you have any concerns.
I'm using the ray-casting algorithm:
https://github.com/substack/point-in-polygon/blob/master/index.js
With that, all we need to do is check that the corners of the child are not inside the parent and that the parent is inside the child, that is all.
I'm no expert with FabricJS so my best might not be much...
but below is my attempt to get your code going.
<canvas id="canvas" width="500" height="350"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<script>
var canvas = new fabric.Canvas("canvas");
canvas.stateful = true;
function getCoords(rect) {
var x = rect.left;
var y = rect.top;
var angle = (rect.angle * Math.PI) / 180;
var coords = [{ x, y }];
x += rect.width * Math.cos(angle);
y += rect.width * Math.sin(angle);
coords.push({ x, y });
angle += Math.PI / 2;
x += rect.height * Math.cos(angle);
y += rect.height * Math.sin(angle);
coords.push({ x, y });
angle += Math.PI / 2;
x += rect.width * Math.cos(angle);
y += rect.width * Math.sin(angle);
coords.push({ x, y });
return coords;
}
function inside(p, vs) {
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i].x, yi = vs[i].y;
var xj = vs[j].x, yj = vs[j].y;
var intersect =
yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}
var parent = new fabric.Rect({
width: 150, height: 100, left: 200, top: 50, angle: 25, selectable: false, fill: "red"
});
var pCoords = getCoords(parent);
var child = new fabric.Rect({
width: 250, height: 175, left: 180, top: 10, angle: 25, hasControls: false, fill: "rgba(0,0,255,0.9)"
});
canvas.add(parent);
canvas.add(child);
canvas.on("object:moving", function (e) {
var cCoords = getCoords(e.target);
var inBounds = true;
cCoords.forEach(c => { if (inside(c, pCoords)) inBounds = false; });
pCoords.forEach(c => { if (!inside(c, cCoords)) inBounds = false; });
if (inBounds) {
e.target.setCoords();
e.target.saveState();
e.target.set("fill", "rgba(0,0,255,0.9)");
} else {
e.target.set("fill", "black");
e.target.animate({
left: e.target._stateProperties.left,
top: e.target._stateProperties.top
},{
duration: 500,
onChange: canvas.renderAll.bind(canvas),
easing: fabric.util.ease["easeInBounce"],
onComplete: function() {
e.target.set("fill", "rgba(0,0,255,0.9)");
}
});
}
});
</script>
That code is on sandbox as well:
https://codesandbox.io/s/force-contain-of-object-inside-another-object-fabric-js-dnvb5
It certainly is nice not to worry about coding all the click/hold/drag fabric makes that real easy...
I was experimenting with FabricJS and there a nice property of the canvas
(canvas.stateful = true;)
that allows us to keep track of where we've been, and if we go out of bounds we can revert that movement, also playing with animate that gives the user visual feedback that the movement is not allowed.
Here is another version without animation:
<canvas id="canvas" width="500" height="350"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/3.6.2/fabric.min.js"></script>
<script>
var canvas = new fabric.Canvas("canvas");
canvas.stateful = true;
function getCoords(rect) {
var x = rect.left;
var y = rect.top;
var angle = (rect.angle * Math.PI) / 180;
var coords = [{ x, y }];
x += rect.width * Math.cos(angle);
y += rect.width * Math.sin(angle);
coords.push({ x, y });
angle += Math.PI / 2;
x += rect.height * Math.cos(angle);
y += rect.height * Math.sin(angle);
coords.push({ x, y });
angle += Math.PI / 2;
x += rect.width * Math.cos(angle);
y += rect.width * Math.sin(angle);
coords.push({ x, y });
return coords;
}
function inside(p, vs) {
var inside = false;
for (var i = 0, j = vs.length - 1; i < vs.length; j = i++) {
var xi = vs[i].x, yi = vs[i].y;
var xj = vs[j].x, yj = vs[j].y;
var intersect =
yi > p.y !== yj > p.y && p.x < ((xj - xi) * (p.y - yi)) / (yj - yi) + xi;
if (intersect) inside = !inside;
}
return inside;
}
var parent = new fabric.Rect({
width: 150, height: 100, left: 200, top: 50, angle: 25, selectable: false, fill: "red"
});
var pCoords = getCoords(parent);
var child = new fabric.Rect({
width: 250, height: 175, left: 180, top: 10, angle: 25, hasControls: false, fill: "rgba(0,0,255,0.9)"
});
canvas.add(parent);
canvas.add(child);
canvas.on("object:moving", function (e) {
var cCoords = getCoords(e.target);
var inBounds = true;
cCoords.forEach(c => { if (inside(c, pCoords)) inBounds = false; });
pCoords.forEach(c => { if (!inside(c, cCoords)) inBounds = false; });
if (inBounds) {
e.target.setCoords();
e.target.saveState();
} else {
e.target.left = e.target._stateProperties.left;
e.target.top = e.target._stateProperties.top;
}
});
</script>
This algorithm also opens the door for other shapes as well, here is a hexagon version:
https://raw.githack.com/heldersepu/hs-scripts/master/HTML/canvas_contained2.html
you can just create the new Class, with your object inside, and do all actions only with parent of the children, something like this:
fabric.RectWithRect = fabric.util.createClass(fabric.Rect, {
type: 'rectWithRect',
textOffsetLeft: 0,
textOffsetTop: 0,
_prevObjectStacking: null,
_prevAngle: 0,
minWidth: 50,
minHeight: 50,
_currentScaleFactorX: 1,
_currentScaleFactorY: 1,
_lastLeft: 0,
_lastTop: 0,
recalcTextPosition: function () {
//this.insideRect.setCoords();
const sin = Math.sin(fabric.util.degreesToRadians(this.angle))
const cos = Math.cos(fabric.util.degreesToRadians(this.angle))
const newTop = sin * this.insideRectOffsetLeft + cos * this.insideRectOffsetTop
const newLeft = cos * this.insideRectOffsetLeft - sin * this.insideRectOffsetTop
const rectLeftTop = this.getPointByOrigin('left', 'top')
this.insideRect.set('left', rectLeftTop.x + newLeft)
this.insideRect.set('top', rectLeftTop.y + newTop)
this.insideRect.set('width', this.width - 40)
this.insideRect.set('height', this.height - 40)
this.insideRect.set('scaleX', this.scaleX)
this.insideRect.set('scaleY', this.scaleY)
},
initialize: function (textOptions, rectOptions) {
this.callSuper('initialize', rectOptions)
this.insideRect = new fabric.Rect({
...textOptions,
dirty: false,
objectCaching: false,
selectable: false,
evented: false,
fragmentType: 'rectWidthRect'
});
canvas.bringToFront(this.insideRect);
this.insideRect.width = this.width - 40;
this.insideRect.height = this.height - 40;
this.insideRect.left = this.left + 20;
this.insideRect.top = this.top + 20;
this.insideRectOffsetLeft = this.insideRect.left - this.left
this.insideRectOffsetTop = this.insideRect.top - this.top
this.on('moving', function(e){
this.recalcTextPosition();
})
this.on('rotating',function(){
this.insideRect.rotate(this.insideRect.angle + this.angle - this._prevAngle)
this.recalcTextPosition()
this._prevAngle = this.angle
})
this.on('scaling', function(fEvent){
this.recalcTextPosition();
});
this.on('added', function(){
this.canvas.add(this.insideRect)
});
this.on('removed', function(){
this.canvas.remove(this.insideRect)
});
this.on('mousedown:before', function(){
this._prevObjectStacking = this.canvas.preserveObjectStacking
this.canvas.preserveObjectStacking = true
});
this.on('deselected', function(){
this.canvas.preserveObjectStacking = this._prevObjectStacking
});
}
});
and then just add your element to your canvas as usual:
var rectWithRect = new fabric.RectWithRect(
{
fill: "red",
}, // children rect options
{
left:100,
top:100,
width: 300,
height: 100,
dirty: false,
objectCaching: false,
strokeWidth: 0,
fill: 'blue'
} // parent rect options
);
canvas.add(rectWithRect);
by the way, you can use method like this to create nested elements, text with background and other.
Codesandbox DEMO

Drawing text on canvas by the letter to control the alpha of individual letters while being aligned to centre

I am trying to draw some letters to a canvas in a very specific way - able to target individual letters and apply an alpha.
These words need to be centered on the baseline and aligned center in the canvas and filled with strokeText rather than fill style.
The text also needs to be line broken resulting in eg;
Now, I have tried several ways of getting this out - it works fine (without the fade) when writing out the words (as full words) - however when I attempt to write them out as individual letters I cannot center them correctly. My code is below omitting the alpha on the specific letters, which once I can center things correctly shouldn't be an issue!
I realize the issue is I am trying to draw each letter separately centered at 0 on the canvas and adding letter spacing for each letter, but given the different size of the middle line I cannot figure a way to have them centered!
var can = document.querySelector('canvas'),
ctx = can.getContext('2d');
function drawStroked(text, fontSize, color, offsetX, offsetY) {
let line = text.split('\n');
this.ctx.font = fontSize + 'px ' + 'TimesNewRoman';
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 2;
this.ctx.textBaseline = 'middle';
this.ctx.textAlign = 'center';
let positionX = this.ctx.canvas.width/3;
let positionY = this.ctx.canvas.height/4;
if(offsetX !== 0) {
positionX += offsetX;
}
if(offsetY !== 0) {
positionY += offsetY;
}
for (var i = 0; i < line.length; i++) {
for (var j = 0; j < line[i].length; j++) {
let letterSpacing = 0;
let lineHeight = positionY;
if(line[i][j] === line[i].length) {
lineHeight = lineHeight * i;
}
this.ctx.strokeText(line[i][j], positionX + (letterSpacing + (j*130)), positionY + (i*fontSize));
}
}
}
drawStroked('THIS\nIS THE\nTEXT', 100, '#000', 0, 0);
<canvas width="1000" height="1000"></canvas>
Resulting output & Finished code thanks to Blindman67!
const Hero = class {
constructor(pos, canvas) {
this.position = document.getElementById(pos);
this.canvas = document.getElementById(canvas);
this.height = document.getElementsByClassName('home')[0].clientHeight;
this.width = this.position.offsetWidth;
this.ctx = this.canvas.getContext('2d');
this.title = 'THIS\nIS THE\nTEXT';
this.canvas.width = this.width;
this.canvas.height = this.height;
this._init_ui();
}
// Draw text to text canvas
_init_ui() {
// BLUE
this.drawStroked(300, '#1816ff', -3, 2, 0.95, [1, 5, 9, 12]);
// GREEN
this.drawStroked(300, '#1bff32', 0, 0, 0.95, [1, 5, 9, 12]);
// RED
this.drawStroked(300, '#ff162f', 3, -2, 0.95, [1, 5, 9, 12]);
}
drawStroked(fontSize, color, offsetX, offsetY, textVertSpacing, fade) {
// Random Char's to scramble through --- to do
// let chars = '!<>-_\\/[]{}—=+*^?#________';
// The words
let line = this.title.split('\n');
// Set the font + size
this.ctx.font = fontSize + 'px ' + 'Kommissar';
// Set the colour - NEED TO ADD ALPHA LOGIC
this.ctx.strokeStyle = color;
// Set the stroke width
this.ctx.lineWidth = 1;
// Set the baseline
this.ctx.textBaseline = 'middle';
// Set the align
this.ctx.textAlign = 'center';
let positionX = this.width/2;
let positionY = this.height/4;
positionX += offsetX;
positionY += offsetY;
let charIndex = 0;
for (var i = 0; i < line.length; i++) {
// get the width of the whole line
let width = this.ctx.measureText(line[i]).width;
console.log(width);
// use the width to find start
var textPosX = positionX - width / 2;
for (let j = 0; j < line[i].length; j++) {
// get char
let char = line[i][j];
// get its width
let cWidth = this.ctx.measureText(char).width;
// check if char needs to fade
if (fade.indexOf(charIndex) > -1) {
this.ctx.globalAlpha = 0.2;
} else {
this.ctx.globalAlpha = 1;
}
// draw the char offset by half its width (center)
this.ctx.strokeText(char, textPosX + cWidth / 2, positionY);
// move too the next pos
textPosX += cWidth;
// count the char
charIndex += 1;
}
// move down one line
positionY += fontSize * textVertSpacing;
}
}
};
export default Hero;
Use ctx.measureText
You need to use ctx.measureText and get the the width of each character, then you can space them correctly.
Correct char spacing
Because you have alignment center, you have to move the character half its width, then draw it and then move half the width again. The spacing between character's centers is half the width of each added. So if a "I" is 20 pixels wide and a "W" is 60 then the space between them is 10 + 30 = 40;
Fade characters
To do the fade I passed an array with the index of the characters to fade. Each Time I draw a character I count it. To check if a character should fade I check the index array for the character count. If they match then fade that character.
See the example for more information
Simple example...
...of what I think you want. I added two red lines to make sure the alignment was correct.
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#FDD"; // mark the center
ctx.fillRect(canvas.width / 2 | 0, 0, 1, canvas.height);
ctx.fillRect(0, canvas.height / 2 | 0, canvas.width, 1);
ctx.fillStyle = "black";
// textVertSpacing is fraction of FontSize
// fade is the index of characters to fade, including spaces
// centerX and y is center of all text
function drawStroked(text, fontSize, color, centerX, centerY, textVertSpacing, fade) {
let line = text.split('\n');
ctx.font = fontSize + 'px ' + 'TimesNewRoman';
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
// to count each character
var charIndex = 0;
// find the top ypos and then move down half a char space
var yPos = centerY - fontSize * line.length * 0.5 * textVertSpacing + fontSize * textVertSpacing / 2;
for (var i = 0; i < line.length; i++) {
// get the width of the whole line
var width = ctx.measureText(line[i]).width;
// use the width to find start
var textPosX = centerX - width / 2;
for (var j = 0; j < line[i].length; j++) {
// get char
var char = line[i][j];
// get its width
var cWidth = ctx.measureText(char).width;
// check if char needs to fade
if (fade.indexOf(charIndex) > -1) {
ctx.globalAlpha = 0.5;
} else {
ctx.globalAlpha = 1;
}
// draw the char offset by half its width (center)
ctx.fillText(char, textPosX + cWidth / 2, yPos);
// move too the next pos
textPosX += cWidth;
// count the char
charIndex += 1
}
// move down one line
yPos += fontSize * textVertSpacing;
}
}
drawStroked('THIS\nIS THE\nTEXT', 60, '#000', canvas.width / 2, canvas.height / 2, 0.9, [2, 4, 8, 12]);
<canvas id="canvas" width="500" height="200"></canvas>
Update
Added some flicker to text by adding an animation loop and calling the text rendering function every few frames. The flicker is done by randomizing the alpha. See snippet below for more info.
requestAnimationFrame(animLoop);
const flickerRate = 4; // change alpha every 4 frames
var frameCount = 0;
const ctx = canvas.getContext('2d');
ctx.fillStyle = "#FDD"; // mark the center
ctx.fillRect(canvas.width / 2 | 0, 0, 1, canvas.height);
ctx.fillRect(0, canvas.height / 2 | 0, canvas.width, 1);
ctx.fillStyle = "black";
function drawStroked(text, fontSize, color, centerX, centerY, textVertSpacing, fade) {
let line = text.split('\n');
ctx.font = fontSize + 'px ' + 'TimesNewRoman';
ctx.strokeStyle = color;
ctx.lineWidth = 2;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
var charIndex = 0;
var yPos = centerY - fontSize * line.length * 0.5 * textVertSpacing + fontSize * textVertSpacing / 2;
for (var i = 0; i < line.length; i++) {
var width = ctx.measureText(line[i]).width;
var textPosX = centerX - width / 2;
for (var j = 0; j < line[i].length; j++) {
var char = line[i][j];
var cWidth = ctx.measureText(char).width;
ctx.globalAlpha = fade.indexOf(charIndex) > -1 ? Math.random()* 0.5+0.25 : 1;
ctx.fillText(char, textPosX + cWidth / 2, yPos);
textPosX += cWidth;
charIndex += 1
}
yPos += fontSize * textVertSpacing;
}
}
function animLoop(){
if((frameCount % flickerRate) === 0){
ctx.clearRect(0,0,canvas.width,canvas.height);
drawStroked('THIS\nIS THE\nTEXT', 60, '#000', canvas.width / 2, canvas.height / 2, 0.9, [2, 4, 8, 12]);
}
frameCount ++;
requestAnimationFrame(animLoop);
}
<canvas id="canvas" width="500" height="200"></canvas>
Using this in a webpack setup so sorry it doesnt run!
// Code in UTIL
getRandomInt(max) {
return Math.floor(Math.random() * (max - 0 + 1)) + 0;
};
const $window = $(window);
let running = false;
const Hero = {
init() {
this.home = $('#home');
this.position = $('#hero');
this.canvas = $('#title');
this.ctx = this.canvas[0].getContext('2d');
this.width = this.position.width();
this.height = this.home.height();
this.ctx.lineWidth = 1.5;
this.fontSize = null;
this.letterSpacing = null;
if(this.position.lenth === 0) {
return;
}
if(running) {
return;
}
// Set hero opacity to 0 for animation
// $('#hero').css('opacity', 0);
this.size();
$window.on('resize', () => {
clearTimeout(this.debounce);
this.debounce = setTimeout( () => {
this.height = this.home.height();
this.width = this.position.width();
this.size();
}, 50);
});
},
size() {
running = true;
this.canvas[0].width = this.width;
this.canvas[0].height = this.height;
if(this.width < 1000) {
this.fontSize = 150;
this.letterSpacing = 5;
} else {
this.fontSize = 300;
this.letterSpacing = 30;
}
},
animate(frames) {
var frameCount = frames || 0;
const flickerRate = 4;
const fade = [Utils.getRandomInt(13), Utils.getRandomInt(13)];
if((frameCount % flickerRate) === 0){
this.ctx.clearRect(0, 0, this.width, this.height);
// Blue
this.drawStroked(this.fontSize, '#0426ff', -2, 2, true, fade);
// Green
this.drawStroked(this.fontSize, '#04ffae', 1, 2, true, fade);
// Pink
this.drawStroked(this.fontSize, '#ff29ad', 0, 0, true, fade);
// White
this.drawStroked(this.fontSize, '#fff', 0, 0, true, fade);
}
frameCount ++;
console.log(frameCount);
// requestAnimationFrame(this.animate);
setTimeout(() => {
this.animate(frameCount);
}, 0.5);
},
drawStroked(fontSize, color, offsetX, offsetY, flicker, fade) {
let line = 'CODE\nIN THE\nDARK'.split('\n'),
chars = line.join('');
// Set the font + size
this.ctx.font = fontSize + 'px ' + 'Kommissar';
// Set the colour
this.ctx.strokeStyle = color;
// Set the baseline
this.ctx.textBaseline = 'middle';
// Set the align
this.ctx.textAlign = 'center';
let letterSpacing = this.letterSpacing,
positionX = (this.width/2 + letterSpacing) + offsetX,
positionY = (this.height/4) + offsetY,
charIndex = 0;
for (var i = 0; i < line.length; i++) {
// get the width of the whole line
let width = this.ctx.measureText(line[i]).width;
// use the width to find start
var textPosX = positionX - width / 2;
for (let j = 0; j < line[i].length; j++) {
// get char
let char = line[i][j];
// get its width
let cWidth = this.ctx.measureText(char).width;
// check if char needs to fade
if(flicker) {
this.ctx.globalAlpha = fade.indexOf(charIndex) > -1 ? Math.random() * 0.5 + 0.25 : 0;
} else {
this.ctx.globalAlpha = 1;
}
// draw the char offset by half its width (center)
this.ctx.shadowColor = color;
this.ctx.shadowBlur = 15;
this.ctx.strokeText(char, textPosX + cWidth / 2, positionY);
// move too the next pos
textPosX += cWidth;
// count the char
charIndex += 1;
}
// move down one line
positionY += fontSize * 1.05;
}
}
};
export default Hero;
#home {
width: 100%;
#hero {
position: absolute;
z-index: 5;
top: 0;
left: 0;
width: 100%;
padding: 30px 0;
> canvas {
margin: 0 auto;
display: block;
}
}
}
<div id="home">
<div id="hero">
<canvas id="title"></canvas>
</div>
</div>

In round range slider, how can i change background color of the changed path?

I have got this function but i am unable to get the path to which the slider when dragged should change the background color of the circle...
$(document).ready(function () {
$("#circle").append('<canvas id="slideColorTracker" class="slideColor"></canvas>');
var canvas = document.getElementById("slideColorTracker");
var context = canvas.getContext('2d');
var $circle = $("#circle"),
$handler = $("#handler"),
$p = $("#test").val(),
handlerW2 = $handler.width() / 2,
radius = ($circle.width()/2)+10,
offset = $circle.offset(),
elementPos = { x: offset.left, y: offset.top },
mHold = 0,
PI = Math.PI / 180
$handler.mousedown(function () {
mHold = 1;
var dim = $("#slideColorTracker");
if ($(".slideColor").length > 1) {
$("#slideColorTracker").remove();
}
});
$(document).mousemove(function (e){
move(e);
}).mouseup(function () { mHold = 0 });
function move(e) {
if (mHold) {
debugger
var deg = 180;
var startAngle = 4.72;
var mousePos = { x: e.pageX - elementPos.x, y: e.pageY - elementPos.y }
var atan = Math.atan2(mousePos.x - radius, mousePos.y - radius);
var deg = -atan / PI + 180;
var percentage = (deg * 100 / 360) | 0;
var endAngle = percentage;
var X = Math.round(radius * Math.sin(deg * PI)),
Y = Math.round(radius * -Math.cos(deg * PI));
var cw = X + radius - handlerW2 - 10; //context.canvas.width /2;
var ch = Y + radius - handlerW2 - 10;
$handler.css({
left: X + radius - handlerW2 - 10,
top: Y + radius - handlerW2 - 10,
})
context.beginPath();
context.arc(($circle.width() / 2), ($circle.height() / 2), radius+20, startAngle, endAngle, false);
context.lineWidth = 15;
context.strokeStyle = 'black';
context.stroke();
$("#test").val(percentage);
$circle.css({ borderColor: "hsl(200,70%," + (percentage * 70 / 100 + 30) + "%)" });
}
}
});
I do not want to use any plugin!
Any help will be much appreciated, since i am new to jquery!
Your $handler does not drag for me unless I change it to this:
$handler.css({
left: mousePos.x,
top: mousePos.y,
position:'absolute'
})
When dragged passed $cirlce.width / 2 it does stroke it correctly.

Javascript - interactive particle logo not working

I'm working through instructions to construct an interactive particle logo design and can't seem to get to the finished product. This is the logo image file -
I'm using a canvas structure / background. Here's the code -
var canvasInteractive = document.getElementById('canvas-interactive');
var canvasReference = document.getElementById('canvas-reference');
var contextInteractive = canvasInteractive.getContext('2d');
var contextReference = canvasReference.getContext('2d');
var image = document.getElementById('img');
var width = canvasInteractive.width = canvasReference.width = window.innerWidth;
var height = canvasInteractive.height = canvasReference.height = window.innerHeight;
var logoDimensions = {
x: 500,
y: 500
};
var center = {
x: width / 2,
y: height / 2
};
var logoLocation = {
x: center.x - logoDimensions.x / 2,
y: center.y - logoDimensions.y / 2
};
var mouse = {
radius: Math.pow(100, 2),
x: 0,
y: 0
};
var particleArr = [];
var particleAttributes = {
friction: 0.95,
ease: 0.19,
spacing: 6,
size: 4,
color: "#ffffff"
};
function Particle(x, y) {
this.x = this.originX = x;
this.y = this.originY = y;
this.rx = 0;
this.ry = 0;
this.vx = 0;
this.vy = 0;
this.force = 0;
this.angle = 0;
this.distance = 0;
}
Particle.prototype.update = function() {
this.rx = mouse.x - this.x;
this.ry = mouse.y - this.y;
this.distance = this.rx * this.rx + this.ry * this.ry;
this.force = -mouse.radius / this.distance;
if (this.distance < mouse.radius) {
this.angle = Math.atan2(this.ry, this.rx);
this.vx += this.force * Math.cos(this.angle);
this.vy += this.force * Math.sin(this.angle);
}
this.x += (this.vx *= particleAttributes.friction) + (this.originX - this.x) * particleAttributes.ease;
this.y += (this.vy *= particleAttributes.friction) + (this.originY - this.y) * particleAttributes.ease;
};
function init() {
contextReference.drawImage(image, logoLocation.x, logoLocation.y);
var pixels = contextReference.getImageData(0, 0, width, height).data;
var index;
for (var y = 0; y < height; y += particleAttributes.spacing) {
for (var x = 0; x < width; x += particleAttributes.spacing) {
index = (y * width + x) * 4;
if (pixels[++index] > 0) {
particleArr.push(new Particle(x, y));
}
}
}
};
init();
function update() {
for (var i = 0; i < particleArr.length; i++) {
var p = particleArr[i];
p.update();
}
};
function render() {
contextInteractive.clearRect(0, 0, width, height);
for (var i = 0; i < particleArr.length; i++) {
var p = particleArr[i];
contextInteractive.fillStyle = particleAttributes.color;
contextInteractive.fillRect(p.x, p.y, particleAttributes.size, particleAttributes.size);
}
};
function animate() {
update();
render();
requestAnimationFrame(animate);
}
animate();
document.body.addEventListener("mousemove", function(event) {
mouse.x = event.clientX;
mouse.y = event.clientY;
});
document.body.addEventListener("touchstart", function(event) {
mouse.x = event.changedTouches[0].clientX;
mouse.y = event.changedTouches[0].clientY;
}, false);
document.body.addEventListener("touchmove", function(event) {
event.preventDefault();
mouse.x = event.targetTouches[0].clientX;
mouse.y = event.targetTouches[0].clientY;
}, false);
document.body.addEventListener("touchend", function(event) {
event.preventDefault();
mouse.x = 0;
mouse.y = 0;
}, false);
html,
body {
margin: 0px;
position: relative;
background-color: #000;
}
canvas {
display: block;
position: absolute;
top: 0;
left: 0;
z-index: 1;
}
img {
display: none;
width: 70%;
height: 400px;
position: absolute;
left: 50%;
transform: translate(-50%, 30%);
}
<html>
<body>
<canvas id="canvas-interactive"></canvas>
<canvas id="canvas-reference"></canvas>
<img src="https://i.stack.imgur.com/duv9h.png" alt="..." id="img">
</body>
</html>
My understanding is the image file has to be set to display: none; and then the image needs to be re-drawn using the javascript commands but I'm not sure if this image is compatible or not. When finished I want the image on a white background.
By way of an example the end design needs to resemble this - Logo particle design
Particle positions from bitmap.
To get the FX you want you need to create a particle system. This is just an array of objects, each with a position, the position where they want to be (Home), a vector defining their current movement, and the colour.
You get each particle's home position and colour by reading pixels from the image. You can access pixel data by rendering an image on a canvas and the using ctx.getImageData to get the pixel data (Note image must be on same domain or have CORS headers to access pixel data). As you read each pixel in turn, if not transparent, create a particle for that pixel and set it colour and home position from the pixels colour and position.
Use requestAnimationFrame to call a render function that every frame iterates all the particles moving them by some set of rules that give you the motion you are after. Once you have move each particle, render them to the canvas using simple shapes eg fillRect
Mouse interaction
To have interaction with the mouse you will need to use mouse move events to keep track of the mouse position relative to the canvas you are rendering to. As you update each particle you also check how far it is from the mouse. You can then push or pull the particle from or to the mouse (depending on the effect you want.
Rendering speed will limit the particle count.
The only issue with these types of FX is that you will be pushing the rendering speed limits as the particle count goes up. What may work well on one machine, will run very slow on another.
To avoid being too slow, and not looking good on some machines you should consider keeping an eye on the frame rate and reducing the particle count if it runs slow. To compensate you can increase the particle size or even reduce the canvas resolution.
The bottleneck is the actual rendering of each particle. When you get to large numbers the path methods really grinds down. If you want really high numbers you will have to render pixels directly to the bitmap, using the same method as reading but in reverse of course.
Example simple particles read from bitmap.
The example below uses text rendered to a canvas to create the particles, and to use an image you would just draw the image rather than the text. The example is a bit overkill as I ripped it from an old answer of mine. It is just as an example of the various ways to get stuff done.
const ctx = canvas.getContext("2d");
const Vec = (x, y) => ({x, y});
const setStyle = (ctx,style) => { Object.keys(style).forEach(key => ctx[key] = style[key]) }
const createImage = (w,h) => {var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i}
const textList = ["Particles"];
var textPos = 0;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
var started = false;
requestAnimationFrame(update);
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
mouse.x = e.pageX;
mouse.y = e.pageY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse"+name,mouseEvents));
function onResize(){
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
if (!started) { startIt() }
}
function update(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
if (w !== innerWidth || h !== innerHeight){ onResize() }
else { ctx.clearRect(0,0,w,h) }
particles.update();
particles.draw();
requestAnimationFrame(update);
}
function createParticles(text){
createTextMap(
text, 60, "Arial",
{ fillStyle : "#FF0", strokeStyle : "#F00", lineWidth : 2, lineJoin : "round", },
{ top : 0, left : 0, width : canvas.width, height : canvas.height }
)
}
// This function starts the animations
function startIt(){
started = true;
const next = ()=>{
var text = textList[(textPos++ ) % textList.length];
createParticles(text);
setTimeout(moveOut,text.length * 100 + 12000);
}
const moveOut = ()=>{
particles.moveOut();
setTimeout(next,2000);
}
setTimeout(next,0);
}
// the following function create the particles from text using a canvas
// the canvas used is displayed on the main canvas top left fro reference.
var tCan = createImage(100, 100); // canvas used to draw text
function createTextMap(text,size,font,style,fit){
const hex = (v)=> (v < 16 ? "0" : "") + v.toString(16);
tCan.ctx.font = size + "px " + font;
var width = Math.ceil(tCan.ctx.measureText(text).width + size);
tCan.width = width;
tCan.height = Math.ceil(size *1.2);
var c = tCan.ctx;
c.font = size + "px " + font;
c.textAlign = "center";
c.textBaseline = "middle";
setStyle(c,style);
if (style.strokeStyle) { c.strokeText(text, width / 2, tCan.height / 2) }
if (style.fillStyle) { c.fillText(text, width / 2, tCan.height/ 2) }
particles.empty();
var data = c.getImageData(0,0,width,tCan.height).data;
var x,y,ind,rgb,a;
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 ++])}`;
particles.add(Vec(x, y), Vec(x, y), rgb);
}
}
}
particles.sortByCol
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.
}
// basic particle
const particle = { pos : null, delta : null, home : null, col : "black", }
// array of particles
const particles = {
items : [], // actual array of particles
mouseFX : { power : 12,dist :110, curve : 2, on : true },
fx : { speed : 0.3, drag : 0.6, size : 4, jiggle : 1 },
// direction 1 move in -1 move out
direction : 1,
moveOut () {this.direction = -1; return this},
moveIn () {this.direction = 1; return this},
length : 0,
each(callback){ // custom iteration
for(var i = 0; i < this.length; i++){ callback(this.items[i],i) }
return this;
},
empty() { this.length = 0; return this },
deRef(){ this.items.length = 0; this.length = 0 },
sortByCol() { this.items.sort((a,b) => a.col === b.col ? 0 : a.col < b.col ? 1 : -1 ) },
add(pos, home, col){ // adds a particle
var p;
if(this.length < this.items.length){
p = this.items[this.length++];
p.home.x = home.x;
p.home.y = home.y;
p.delta.x = 0;
p.delta.y = 0;
p.col = col;
}else{
this.items.push( Object.assign({}, particle,{ pos, home, col, delta : Vec(0,0) } ) );
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;
const mP = this.mouseFX.power;
const mD = this.mouseFX.dist;
const mC = this.mouseFX.curve;
const fxJ = this.fx.jiggle;
const fxD = this.fx.drag;
const 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;
},
}
canvas { position : absolute; top : 0px; left : 0px; background : black;}
<canvas id="canvas"></canvas>
Use png saved as PNG-8 and and allow cross-origin
I saw the cool article from Bricks and mortar and thought I'd try it out.
I battled with it for an eternity, thinking that my js was wrong... Turns out that the image has to be saved as a PNG-8 without dither instead of a PNG-24.
Then make sure that you add the crossOrigin="Anonymous" attribute to the image tag:
<img crossOrigin="Anonymous" id="img" src="[link to wherever you host the image]" alt="logo">
I also hid the reference canvas by adding the following styles:
canvas#canvas-reference {
display: none;
}
I also added a debounce and resize function, so it's responsive.
The result:
See Demo with inverted logo

Get cursor location of a rectangle inside a canvas

i have a canvas, inside of which i have a board/grid. When a user highlights their mouse over an intersection of the grid, i want it to show where their game peice will go. This worked perfectly fine when the board was the exact size of the canvas. I made it abit smaller by x all the way round.
So as you can see in the picture below, the green shows the canvas and the grid is the board. I put my cursor at the very bottom right corner of the green to show when it triggers. The only one that works fine is the middle one because regardless how big i make the board, the middle will always be the middle.
Any easy fix would just be to make the area with the mouseover event, the dimensions of the board instead of the canvas but the event listener is on the canvas. My code is below the image
Variables:
var canvas = document.getElementById("game-canvas");
var context = canvas.getContext("2d");
var boardSize = 13;
var border = canvas.width / 20;
var boardWidth = canvas.width - (border * 2);
var boardHeight = canvas.height - (border * 2);
var cellWidth = boardWidth / (boardSize - 1);
var cellHeight = boardHeight / (boardSize - 1);
var lastX;
var lastY;
Mouse over event:
canvas.addEventListener('mousemove', function(evt)
{
var position = getGridPoint(evt);
if ((position.x != lastX) || (position.y != lastY))
{
placeStone((position.x * cellWidth) + border, (position.y * cellWidth) + border, 'rgba(0, 0, 0, 0.2)');
}
lastX = position.x;
lastY = position.y;
});
Gets the point on the grid and converts that into a number 0 - 13 (in this case)
function getGridPoint(evt)
{
var rect = canvas.getBoundingClientRect();
var x = Math.round((evt.clientX-rect.left)/(rect.right-rect.left)*boardWidth);
var y = Math.round((evt.clientY-rect.top)/(rect.bottom-rect.top)*boardHeight);
var roundX = Math.round(x / cellWidth);
var roundY = Math.round(y / cellHeight);
return {
x: roundX,
y: roundY
};
}
And finally draws the piece on the board:
function placeStone(x, y, color)
{
var radius = cellWidth / 2;
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fillStyle = color;
context.fill();
context.lineWidth = 5;
}
I left a couple bits out like how the grid refreshs so its not a string of circles following your mouse and stuff, to keep it as short as i can, im hoping its just a simple asnwer and nobody needs to recreate it but if you do i can include the function that refreshes the grid and draws everything. Thankyou for any advice
To get the position relative to a box
// just as an example w,h are width and height
const box = { x : 10, y : 10, w : 100, h : 100 };
// mouse is the mouse coords and relative to the topleft of canvas (0,0);
var mouse.box = {}
mouse.box.x = mouse.x - box.x;
mouse.box.y = mouse.y - box.y;
Negative values for mouse.box x,y and values greater than box width and height have mouse outside.
For more convenience you can get the mouse normalize pos in the box
mouse.box.nx = mouse.box.x / box.w;
mouse.box.ny = mouse.box.y / box.h;
The coords for nx,ny are in the range 0-1 when inside or on the edge of the box;
If you want to have grid positions then define the grid
box.gridW = 10; // grid divisions width
box.gridH = 10; // grid divisions height
Then getting the grid pos of mouse
mouse.box.gx = Math.floor(mouse.box.nx * box.gridW);
mouse.box.gy = Math.floor(mouse.box.ny * box.gridH);
const ctx = canvas.getContext("2d");
const box = { x : 50,y : 10, w : 200, h : 200, gridW : 10, gridH : 10}
function drawGrid(){
var sx = box.w / box.gridW;
var sy = box.h / box.gridH;
var bx = box.x;
var by = box.y;
for(var y = 0; y < box.gridH; y ++){
for(var x = 0; x < box.gridW; x ++){
ctx.strokeRect(x * sx + bx, y * sx + by,sx,sy);
}
}
if(mouse.box){
if(mouse.box.nx >= 0 && mouse.box.nx <= 1 &&
mouse.box.ny >= 0 && mouse.box.ny <= 1){
ctx.fillRect(mouse.box.gx * sx + bx, mouse.box.gy * sx + by,sx,sy);
}
}
}
const mouse = {};
canvas.addEventListener("mousemove",(e)=>{
mouse.x = e.pageX;
mouse.y = e.pageY;
});
function updateMouse(){
if(!mouse.box){
mouse.box = {};
}
mouse.box.x = mouse.x - box.x;
mouse.box.y = mouse.y - box.y;
mouse.box.nx = mouse.box.x / box.w;
mouse.box.ny = mouse.box.y / box.h;
mouse.box.gx = Math.floor(mouse.box.nx * box.gridW);
mouse.box.gy = Math.floor(mouse.box.ny * box.gridH);
var p = 20;
ctx.fillText("x : " + mouse.x,box.x+box.w+10,p); p+= 14;
ctx.fillText("y : " + mouse.y,box.x+box.w+10,p); p+= 20;
ctx.fillText("Box relative",box.x+box.w+10,p); p+= 14;
ctx.fillText("x : " + mouse.box.x,box.x+box.w+10,p); p+= 14;
ctx.fillText("y : " + mouse.box.y,box.x+box.w+10,p); p+= 14;
ctx.fillText("nx : " + mouse.box.nx,box.x+box.w+10,p); p+= 14;
ctx.fillText("ny : " + mouse.box.ny,box.x+box.w+10,p); p+= 14;
ctx.fillText("gx : " + mouse.box.gx,box.x+box.w+10,p); p+= 14;
ctx.fillText("gy : " + mouse.box.gy,box.x+box.w+10,p); p+= 14;
}
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){ // resize canvas if window size has changed
canvas.width = innerWidth;
canvas.height = innerHeight;
}
ctx.setTransform(1,0,0,1,0,0); // set default transform
ctx.clearRect(0,0,canvas.width,canvas.height); // clear the canvas
updateMouse();
drawGrid();
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=canvas><canvas>

Categories