Related
After looking through similar questions posted to the forum and not finding something that helped me solve my own problem, I'm posting it.
I'm using SVG.js to generate SVG shapes in a web document. I'd like one of those shapes to ”follow” the mouse/cursor.
By that I mean: The shape has a fixed position/anchor point (at its original center) and it can only move a limited distance (let's say 50px) away from this fixed point.
I want the shape to move in the direction of the cursor, whenever the cursor moves, but never further than a defined distance away from its orignal position. I'm attaching a short animation to illustrate my description:
If the cursor were to disappear, the shape would snap back to its original center.
I know my way around Javascript, HTML and CSS. This type of element-manipulation is new to me and the math is giving my quite the headache, any help would be great.
It looks like I need the shape to basically rotate around its original center, with an angle relative to the cursor? I'm really unsure how to solve this. I have tried using a method to calculate the angle described in this post. My shape moves, but not as intended:
// init
var draw = SVG().addTo('body')
// draw
window.shape = draw.circle(25, 25).stroke({
color: '#000',
width: 2.5
}).fill("#fff");
shape.attr("id", "circle1");
shape.move(50, 50)
// move
var circle = $("#circle1");
var dist = 10;
$(document).mousemove(function(e) {
// angle
var circleCenter = [circle.offset().left + circle.width() / 2, circle.offset().top + circle.height() / 2];
var angle = Math.atan2(e.clientX - circleCenter[0], -(e.clientY - circleCenter[1])) * (180 / Math.PI);
var x = Math.sin(angle) * dist;
var y = (Math.cos(angle) * dist) * -1;
shape.animate().dmove(x, y);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Note: It does not matter to me whether the solution depends on jQuery or not (ideally it doesn't).
After more fiddling around with some solutions to calculating angles and distances, I found the answer.
I'm using a fixed reference point to calculate the angle of the direct line between the center of the shape and the cursor. Then I move the shape relative to this reference point and by a given amount:
// Init canvas
var draw = SVG().addTo('body')
// Draw reference/anchor
var shape_marker_center = draw.circle(3,3).fill("#f00").move(150, 150);;
var grafikCenter = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")]
// Draw shapes
var shape = draw.circle(25, 25).stroke({color: '#000', width: 2.5 }).fill("none");
shape.attr("id", "circle1").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape2 = draw.circle(50, 50).stroke({color: '#000', width: 2.5 }).fill("none");
shape2.attr("id", "circle2").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
var shape3 = draw.circle(75, 75).stroke({color: '#000', width: 2.5 }).fill("none");
shape3.attr("id", "circle3").attr({cx: grafikCenter[0], cy:grafikCenter[1]})
$(document).mousemove(function(e) {
var pointA = [shape_marker_center.attr("cx"), shape_marker_center.attr("cy")];
var pointB = [e.clientX, e.clientY];
var angle = Math.atan2(pointB[1] - pointA[1], pointB[0] - pointA[0]) * 180 / Math.PI ;
//
var distance_x_1 = Math.cos(angle*Math.PI/180) * 16;
var distance_y_1 = Math.sin(angle*Math.PI/180) * 16;
var distance_x_2 = Math.cos(angle*Math.PI/180) * 8;
var distance_y_2 = Math.sin(angle*Math.PI/180) * 8;
//
shape.center((grafikCenter[0] + distance_x_1), (grafikCenter[1] + distance_y_1));
shape2.center((grafikCenter[0] + (distance_x_2) ), (grafikCenter[1] + (distance_y_2)));
})
svg {
width: 100vw;
height: 100vh;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/svg.js/3.0.16/svg.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I have used latex and in particular tikz quite a bit. Using this I was able to create the image shown below.
The following short code was used to create the image.
\documentclass[tikz]{standalone}
\begin{document}
\usetikzlibrary{shapes.geometric}
\usetikzlibrary{backgrounds}
\begin{tikzpicture}[background rectangle/.style={fill=black},
show background rectangle]
\def\pages{
Home,
Events,
Pictures,
Video,
Contact,
About,
Map
}
\def\ngon{7}
\node[regular polygon,regular polygon sides=\ngon,minimum size=3cm] (p) {};
\foreach\page [count=\x] in \pages{\node[color=white, shift={(\x*360/7+35:0.4)}] (p\x) at (p.corner \x){\page};}
\foreach\i in {1,...,\numexpr\ngon-1\relax}{
\foreach\j in {\i,...,\x}{
\draw[thin, orange, dashed] (p\i) -- (p\j);
}
}
\end{tikzpicture}
\end{document}
I have tried for the last few hours to recreate the same image using 'HTMLæ, 'CSS' and 'Javascript'. I used the 'canvas' element to draw the lines, however I ran into a series of problems as can be seen in the image below
Which was made with the following code. I tried to the best of my abilities to minimize the code. The code can be found at the bottom of the post. The code has the following problems
Scalability. The text in the image is not the same as in the 'body' of the page.
The image hides the rest of the text in the body
To place the text outside the figure is hardcoded
The last minor problem is that the first element in the list is not drawn
I would like to address the problems above, but I am unsure how to proceed. Again I am not married to the idea of using canvas (can a better result be done using nodes and elements instead). However, the output should mimic the first image as closely as possible.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Canvas octagon</title>
<style>
* {
margin: 0;
padding: 0;
color:white;
background:black;
}
canvas {
display: block;
}
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
/* Disable scrollbars */
display: block;
/* No floating content on sides */
}
</style>
</head>
<body>
<canvas id="polygon"></canvas>
<h2>more space</h2>
<ol id="poly">
<li>About</li>
<li>Home</li>
<li>Pictures</li>
<li>Video</li>
<li>Events</li>
<li>Map</li>
<li>Apply?</li>
<li>Recepies</li>
</ol>
some more text here
<script>
(function() {
var canvas = document.getElementById('polygon'),
context = canvas.getContext('2d');
// resize the canvas to fill browser window dynamically
window.addEventListener('resize', resizeCanvas, false);
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
/**
* Your drawings need to be inside this function otherwise they will be reset when
* you resize the browser window and the canvas goes will be cleared.
*/
drawStuff();
}
resizeCanvas();
function drawStuff() {
// do your drawing stuff here
context.beginPath();
context.translate(120, 120);
context.textAlign = "center";
var edges = document.getElementById("poly").getElementsByTagName("li");
var sides = edges.length
var angle = (Math.PI * 2) / sides;
var radius = 50;
context.save();
for (var i = 0, item; item = edges[i]; i++) {
console.log("Looping: index ", i, "item " + item.innerText);
var start_x = radius * Math.cos(angle * i);
var start_y = radius * Math.sin(angle * i);
context.lineTo(start_x, start_y);
var new_x_text = 1.4 * radius * Math.cos(angle * i);
var new_y_text = 1.4 * radius * Math.sin(angle * i);
context.fillText(item.innerText, new_x_text, new_y_text);
context.strokeStyle = 'orange';
for (var j = 0; j < i; j++) {
var new_x = radius * Math.cos(angle * j);
var new_y = radius * Math.sin(angle * j);
context.moveTo(start_x, start_y);
context.lineTo(new_x, new_y);
console.log(new_x, new_y);
}
context.fillStyle = 'white'
}
var new_x = radius * Math.cos(0);
var new_y = radius * Math.sin(0);
context.lineTo(new_x, new_y);
context.stroke();
}
})();
</script>
</body>
</html>
Using the canvas to render content
First I will say that using javascript will be longer than if you use some symbolic representation language like Latex. It is designed to do graphical representations with the minimum of fuss. The actual code base that makes it work is substantial but hidden for the general user.
Using the DOM
As the content for the canvas is stored in the DOM it also a good idea to store as much information as you can in the DOM, the colors, fonts, etc can all be stored in an element`s dataset.
For this I have put the settings in the ordered list. It contains all the settings, but there is also a default set of settings in the rendering function. The elements dataset will overwrite the defaults, or you can not add any dataset properties and let it all use the defaults.
Vetting settings
In the example below I have only put a minimum of vetting. People tend to put quotes around everything in the DOM as numbers can sometimes not work if represented as a string, I force all the numbers to the correct type. Though to be safe I should have checked to see if indeed they are valid numbers, the same for the other settings. I have just assumed that they will be correctly formatted.
The function
All the work is done in a function, you pass it the query string needed to find the list and canvas. It then uses the list items to render to the canvas.
Relative sizes
As the canvas size is not always known (it could be scaled via CSS) you need to have some way to specify size independent of pixels. For this I use a relative size. Thus the font size is as a fraction of the canvas size eg data-font-size = 16 means that the font will be 1/16th of the canvas height. The same for the line width, and the dash size is a multiple of the line width. eg data-line-dash = 4 means that the dashes are 4 times the length of the line width.
Element's data properties
To use data set you add the property to the element in the HTML prefixed with the word data- then the property name/s separated by "-". In javascript you can not use "-" directly as part of a variable name (it's a subtract operator) so the property names are converted to camelcase (the same as CSS properties), and stored in the element's dataset property.
<!-- HTML -->
<div id="divElement" data-my-Value = "some data"></div>
<script>
// the property of divElement is available as
console.log(divElement.dataset.myValue); // output >> "some data"
</script>
Scaling & rendering
The canvas is rendered at a ideal size (512 in this case) but the transform is set to ensure that the render fits the canvas. In this example I scale the x and y axis) the result is that the image does not have a fixed aspect.
Background
The canvas is transparent by default, but I do clear it in case you rerender to it. Anything under the canvas should be visible.
I first render the lines, then the text, clearing a space under the text to remove the lines. ctx.clearRect ensure the a canvas rect is transparent.
Drawing lines
To draw the lines you have two loops, From each item you draw a line to every other item. You don't want to draw a line more than once, so the inner loop starts at the current outer loops position + 1. This ensures a line is only rendered one.
Example
The example shows what I think you are after. I have add plenty of comments, but if you have questions do ask in the comments below.
I assumed you wanted the ordered list visible. If not use a CSS rule to hide it, it will not affect the canvas rendering.
Also if you size the canvas via CSS you may get a mismatch between canvas resolution and display size. This can result in blurred pixels, and also some high res displays will set canvas pixels to large. If this is a problem there are plenty of answers on SO on how to deal with blurred canvas rendering and hi res displays (like retina).
function drawConnected(listQ, canvasQ) {
const list = document.querySelector(listQ);
if(list === null){
console.warn("Could not find list '" + listQ +"'");
return;
}
const canvas = document.querySelector(canvasQ);
if(canvas === null){
console.warn("Could not find canvas '" + canvasQ + "'");
return;
}
const ctx = canvas.getContext("2d");
const size = 512; // Generic size. This is scaled to fit the canvas
const xScale = canvas.width / size;
const yScale = canvas.height / size;
// get settings or use dsefault
const settings = Object.assign({
fontSize : 16,
lineWidth : 128,
lineDash : 4,
textColor : "White",
lineColor : "#F90", // orange
startAngle : -Math.PI / 2,
font : "arial",
}, list.dataset);
// calculate relative sizes. convert deg to randians
const fontSize = size / Number(settings.fontSize) | 0; // (| 0 floors the value)
const lineWidth = size / Number(settings.lineWidth) | 0;
const lineDash = lineWidth * Number(settings.lineDash);
const startAngle = Number(settings.startAngle) * Math.PI / 180; // -90 deg is top of screen
// get text in all the list items
const items = [...list.querySelectorAll("li")].map(element => element.textContent);
// Set up the canvas
// Scale the canvas content to fit.
ctx.setTransform(xScale,0,0,yScale,0,0);
ctx.clearRect(0,0,size,size); // clear as canvas may have content
ctx.font = fontSize + "px " + settings.font;
// align text to render from its center
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// set the line details
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.setLineDash([lineDash, lineDash]);
// need to make room for text so calculate all the text widths
const widths = [];
for(let i = 0; i < items.length; i ++){
widths[i] = ctx.measureText(items[i]).width;
}
// use the max width to find a radius that will fit all text
const maxWidth = Math.max(...widths);
const radius = (size/2 - maxWidth * 0.6);
// this function returns the x y position on the circle for item at pos
const getPos = (pos) => {
const ang = pos / items.length * Math.PI * 2 + startAngle;
return [
Math.cos(ang) * radius + size / 2,
Math.sin(ang) * radius + size / 2
];
};
// draw lines first
ctx.strokeStyle = settings.lineColor;
ctx.beginPath();
for(let i = 0; i < items.length; i ++){
const [x,y] = getPos(i);
for(let j = i+1; j < items.length; j ++){
const [x1,y1] = getPos(j);
ctx.moveTo(x,y);
ctx.lineTo(x1,y1);
}
}
ctx.stroke();
// draw text
ctx.fillStyle = settings.textColor;
for(let i = 0; i < items.length; i ++){
const [x,y] = getPos(i);
ctx.clearRect(x - widths[i] * 0.6, y - fontSize * 0.6, widths[i] * 1.2, fontSize * 1.2);
ctx.fillText(items[i],x,y);
}
// restore default transform;
ctx.setTransform(1,0,0,1,0,0);
}
// draw the diagram with selector query for ordered list and canvas
drawConnected("#poly","#polygon");
* {
margin: 0;
padding: 0;
color:white;
background:black;
}
canvas {
display: block;
}
html,
body {
font-family : arial;
width: 100%;
height: 100%;
margin: 0px;
border: 0;
display: block;
}
<canvas id="polygon" width = "256" height = "256"></canvas>
<h2>more space</h2>
<ol id="poly"
data-font-size = 16
data-line-width = 128
data-line-dash = 2
data-text-color = "white"
data-line-color = "#F80"
data-start-angle = "-90"
data-font = "arial"
>
<li>About</li>
<li>Home</li>
<li>Pictures</li>
<li>Video</li>
<li>Events</li>
<li>Map</li>
<li>Apply?</li>
<li>Recepies</li>
</ol>
I'm drawing a curve on an HTML5 canvas and am using alpha transparency to create a glow effect, by drawing a thicker version of the curve underneath with an alpha of less than 1, then drawing a thinner version of the curve on top (and I'm doing this with several levels of recursion).
Okay here's the problem. It works exactly the way I want it to in Chrome, giving a beautiful glow effect. But in Firefox, the alpha doesn't render properly if my browser dimensions are bigger than around 300px in height (yes that sounds crazy but it is actually what it is doing for some reason). If I resize my browser to be extremely tiny, then all the sudden the alpha works and I get my awesome glow. Once I make the window a reasonable size, the alpha no longer works so instead of a glowing line I just get a really thick line. :( Code is below.
HTML:
<body>
<canvas id="viewport">
<script type="text/javascript" src="scripts/render.js"></script>
</body>
CSS:
* {
background-color:#000000;
padding:0px;
margin:0px;
width:100%;
height:100%;
overflow:hidden;
}
#viewport {
border:0px;
}
Javascript:
window.viewport = document.getElementById("viewport");
window.context = viewport.getContext("2d");
window.xFactor = 1;
window.yFactor = 1;
function initializeViewport() {
maximizeViewport();
setFactors();
}
function maximizeViewport() {
viewport.width = window.innerWidth;
viewport.height = window.innerHeight;
}
function setFactors() {
xFactor = window.innerWidth / 100;
yFactor = window.innerHeight / 100;
}
function absX(x) {
return Math.floor(x * xFactor);
}
function absY(y) {
return Math.floor(y * yFactor);
}
function drawQuadraticCurve(startX, startY, controlX, controlY, endX, endY, lineWidth, gradient, alpha, glowiness, glowLevel) {
glowLevel = (typeof glowLevel === 'undefined') ? 0 : glowLevel;
// Draw the glow first
if (glowLevel < glowiness) {
drawQuadraticCurve(startX, startY, controlX, controlY, endX, endY, lineWidth + Math.sqrt(glowLevel), gradient, alpha*0.65, glowiness, glowLevel + 1);
}
// Then draw the curve
context.beginPath();
context.moveTo(absX(startX), absY(startY));
context.quadraticCurveTo(absX(controlX), absY(controlY), absX(endX), absY(endY));
context.lineWidth = lineWidth;
context.strokeStyle = gradient;
context.globalAlpha = alpha;
context.shadowColor = "#FFFFFF";
context.shadowBlur = 0;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.stroke();
}
function createRadialGradient(colors, innerX, innerY, innerR, outerX, outerY, outerR) {
var gradient = context.createRadialGradient(absX(innerX),absY(innerY),Math.min(absX(innerR/2), absY(innerR/2)),absX(outerX),absY(outerY),Math.min(absX(outerR/2), absY(outerR/2)));
var gradientLength = colors.length;
for (i=0; i<gradientLength; i++) {
gradient.addColorStop(colors[i][0], colors[i][1]);
}
return gradient;
}
initializeViewport();
drawQuadraticCurve(80,65,20,70,70,10, 1,createRadialGradient([[0,"#FFFFFF"],[0.7,"#33CCFF"],[1,"#9944FF"]],50,50,1,50,50,90),1,8,0);
Screenshot of it working in Chrome: http://i.imgur.com/brVT2i6.png
Screenshot of it NOT working in Firefox: http://i.imgur.com/63Z4PJY.png
Screenshot of it working in Firefox after I've resized the window to be ridiculously small: http://i.imgur.com/d9AihEu.png
First working solution gets an upvote and a green checkmark! Yay!
Here is a glowing quadratic curve made up of small, individual line segments--each segment being a different color. A shadowColor equal to the segment color causes the glow. The rendering is compatible across browsers (including FF).
(You can control the linewidth and the glow strength)
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// variables to define colors -- use hsl instead of rgb
var hue=10;
var hueShift=4;
// define the quadratic curve
var startPt={x:350,y:100};
var controlPt={x:0,y:250};
var endPt={x:350,y:400};
// variables defining the starting & ending point of
// the current line segment.
var newXY=startPt;
var oldXY=startPt;
// the current interval along the quadratic curve
// (used to calc an x,y along the curve)
// (t is kind-of like a percentage along the curve--kind of but not)
var t=0;
// the unshadowed linewidth
ctx.lineWidth=1;
// the shadow to apply around the line
ctx.shadowBlur=7;
// round the endcaps to visually blend the line segments
ctx.lineCap='round';
// start with a black-filled canvas
ctx.fillStyle='black';
ctx.fillRect(0,0,cw,ch);
// start the animation
requestAnimationFrame(animate);
function animate(time){
// calculate a new x,y along the curve
var T=t/100;
var newXY=getQuadraticBezierXYatT(startPt,controlPt,endPt,T);
// change the color for this segment
hue=(hue+hueShift)%360;
// draw this line segment with a shadow-glow
glowLine(oldXY,newXY,hue);
// set old=new in preparation for the next loop
oldXY=newXY;
// request another animation loop intil reaching 100
if(++t<100){
requestAnimationFrame(animate);
}
}
function glowLine(oldXY,newXY,hue){
// calculate the hsl color given the new hue
var hsl="hsl(" + (hue % 360) + ",99%,50%)";
// draw a glowing line segment
// (==a line segment with a shadow of the same color as the line segment)
ctx.beginPath();
ctx.moveTo(oldXY.x,oldXY.y);
ctx.lineTo(newXY.x,newXY.y);
ctx.fillStyle= hsl
ctx.strokeStyle=hsl;
ctx.shadowColor=hsl;
// overdraw the line segment so it really stands out
for(var i=0;i<6;i++){
ctx.stroke();
}
}
// calculate an [x,y] along a quadratic curve given an interval T
function getQuadraticBezierXYatT(startPt,controlPt,endPt,T) {
var x = Math.pow(1-T,2) * startPt.x + 2 * (1-T) * T * controlPt.x + Math.pow(T,2) * endPt.x;
var y = Math.pow(1-T,2) * startPt.y + 2 * (1-T) * T * controlPt.y + Math.pow(T,2) * endPt.y;
return( {x:x,y:y} );
}
body{ background-color:ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=500 height=500></canvas>
This is really a comment, but it wouldn't fit in the space allocated to a comment. :-)
I've consulted the All-Knowing-Oracle of Html5 Canvas--the w3.org.
If you assign a zero shadowBlur (as you do) the spec says there should be no shadow applied.
That means that FF with the larger canvas size is correctly applying the w3 standard (not drawing any shadow) and both Chrome & FF(smaller version) are incorrectly applying a shadow when it should not.
http://www.w3.org/TR/2dcontext/
Shadows are only drawn if the opacity component of the alpha component
of the color of shadowColor is non-zero and either the shadowBlur is
non-zero, or the shadowOffsetX is non-zero, or the shadowOffsetY is
non-zero.
Therefore, to have cross-browser compatibility, you mustn't rely on quirks in the rendering when shadowBlur=0. You must create your glow in another way within the "rules".
I tried both of these in canvas and nothing showed, also I doubt it is even efficient :/. I am trying to make rain that comes down the screen.. Wondering what is the most efficient way of doing this. I am a beginner at animation and would really appreciate help.
I suspect that creating a rain object would be best, each with the quality of coming down the screen then coming to the top and then an array with them...maybe with random x values withing the canvas width and y values of 0 but I don't know how to implement that. Please help!
xofRain = 20;
startY = 0;
ctx.beginPath();
ctx.moveTo(xofRain, startY);
ctx.lineTo(xofRain, startY + 20);
ctx.closePath();
ctx.fillStyle = "black";
ctx.fill();
function rain(xofRain){
startY = canvas.height();
ctx.moveTo(xofRain, startY);
ctx.beginPath();
ctx.lineTo(xofRain, startY + 3);
ctx.closePath();
ctx.fillStyle = "blue";
ctx.fill();
}
Here comes your answer, this snow rain is created using pure HTML5 Canvas, the technique used to achieve this animation is called "Double Buffer Animation". First it is good to know what is Double Buffer animation technique.
Double Buffer Technique: This is an advanced technique to make animation clear and with less flickers in it. In this technique 2 Canvas is used, one is displayed on webpage to show the result and second one is used to create animation screens in backed process.
How this will help full, suppose we have to create a animation with very high number of move, as in our Snow Fall example, there are number of Flakes are moving with there own speed, so keep them moving, we have to change position of each flake and update it on the canvas, this is quite heavy process to deal with.
So Now instead of updating each Flake directly on our page canvas, we will create a buffer Canvas, where all these changes take place and we just capture a Picture from Buffer canvas after 30ms and display it on our real canvas.
This way our animation will be clear and without flickers. So here is a live example of it.
http://aspspider.info/erishaan8/html5rain/
Here is the code of it:
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>HTML5 Rain</title>
<!--[if IE]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<style>
article, aside, figure, footer, header, hgroup,
menu, nav, section { display: block; }
</style>
<script type="text/javascript">
var canvas = null;
var context = null;
var bufferCanvas = null;
var bufferCanvasCtx = null;
var flakeArray = [];
var flakeTimer = null;
var maxFlakes = 200; // Here you may set max flackes to be created
function init() {
//Canvas on Page
canvas = document.getElementById('canvasRain');
context = canvas.getContext("2d");
//Buffer Canvas
bufferCanvas = document.createElement("canvas");
bufferCanvasCtx = bufferCanvas.getContext("2d");
bufferCanvasCtx.canvas.width = context.canvas.width;
bufferCanvasCtx.canvas.height = context.canvas.height;
flakeTimer = setInterval(addFlake, 200);
Draw();
setInterval(animate, 30);
}
function animate() {
Update();
Draw();
}
function addFlake() {
flakeArray[flakeArray.length] = new Flake();
if (flakeArray.length == maxFlakes)
clearInterval(flakeTimer);
}
function blank() {
bufferCanvasCtx.fillStyle = "rgba(0,0,0,0.8)";
bufferCanvasCtx.fillRect(0, 0, bufferCanvasCtx.canvas.width, bufferCanvasCtx.canvas.height);
}
function Update() {
for (var i = 0; i < flakeArray.length; i++) {
if (flakeArray[i].y < context.canvas.height) {
flakeArray[i].y += flakeArray[i].speed;
if (flakeArray[i].y > context.canvas.height)
flakeArray[i].y = -5;
flakeArray[i].x += flakeArray[i].drift;
if (flakeArray[i].x > context.canvas.width)
flakeArray[i].x = 0;
}
}
}
function Flake() {
this.x = Math.round(Math.random() * context.canvas.width);
this.y = -10;
this.drift = Math.random();
this.speed = Math.round(Math.random() * 5) + 1;
this.width = (Math.random() * 3) + 2;
this.height = this.width;
}
function Draw() {
context.save();
blank();
for (var i = 0; i < flakeArray.length; i++) {
bufferCanvasCtx.fillStyle = "white";
bufferCanvasCtx.fillRect(flakeArray[i].x, flakeArray[i].y, flakeArray[i].width, flakeArray[i].height);
}
context.drawImage(bufferCanvas, 0, 0, bufferCanvas.width, bufferCanvas.height);
context.restore();
}
</script>
</head>
<body onload="init()">
<canvas id="canvasRain" width="800px" height="800px">Canvas Not Supported</canvas>
</body>
</html>
Also if you find this help full, accept as Answer and make it up. o_O
Cheers!!!
I'm not sure what "most efficient" is. If it was me I'd do it in WebGL but whether or not that's efficient is not clear to me.
In either case I'd try to use a stateless formula. Creating and updating state for every raindrop is arguably slow.
const ctx = document.querySelector("canvas").getContext("2d");
const numRain = 200;
function render(time) {
time *= 0.001; // convert to seconds
resizeCanvasToDisplaySize(ctx.canvas);
const width = ctx.canvas.width;
const height = ctx.canvas.height;
ctx.fillStyle = "black";
ctx.fillRect(0, 0, width, height);
resetPseudoRandom();
const speed = time * 500;
ctx.fillStyle = "#68F";
for (let i = 0; i < numRain; ++i) {
const x = pseudoRandomInt(width);
const y = (pseudoRandomInt(height) + speed) % height;
ctx.fillRect(x, y, 3, 8);
}
requestAnimationFrame(render);
}
requestAnimationFrame(render);
let randomSeed_ = 0;
const RANDOM_RANGE_ = Math.pow(2, 32);
function pseudoRandom() {
return (randomSeed_ =
(134775813 * randomSeed_ + 1) %
RANDOM_RANGE_) / RANDOM_RANGE_;
};
function resetPseudoRandom() {
randomSeed_ = 0;
};
function pseudoRandomInt(n) {
return pseudoRandom() * n | 0;
}
function resizeCanvasToDisplaySize(canvas) {
const width = canvas.clientWidth;
const height = canvas.clientHeight;
if (canvas.width !== width || canvas.height !== height) {
canvas.width = width;
canvas.height = height;
}
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>
Note that I could have used ctx.moveTo(x, y); ctx.lineTo(x, y + 8); for each line and then at the end of the loop called ctx.stroke(). I didn't do that because I'm assuming it would be less efficient than using ctx.fillRect. In order for the canvas to draw lines it actually has to allocate a dynamic path (you call ctx.beginPath). It then has to record all the lines you add. Then it has to expand those lines into vertices of various kinds to rasterize the lines. You can basically see the various algorithms it uses here. Conversely none of that has to happen with ctx.fillRect. No allocations have to happen (not saying they don't happen, just saying they don't have to). The canvas can just use a single pre-allocated quad and draw it on the GPU by passing the correct matrix to draw whatever rectangle you ask of it. Of course they're might be more overhead calling ctx.fillRect 200 times rather than ctx.moveTo, ctx.lineTo 200s + ctx.stroke once but really that's up to the browser.
The rain above may or may not be a good enough rain effect. That wasn't my point in posting really. The point is efficiency. Pretty much all games that have some kind of rain effect do some kind of stateless formula for their rain. A different formula would generate different or less repetitive rain. The point is it being stateless.
By combining some CSS and Jquery UI / draggable I have created the ability to pan an image and with a little extra JS you can now zoom the image.
The problem I am having is that, if you zoom in the image's top left corner is fixed, as you would expect. What I would like is for the image to stay central (based on the current pan) so that the middle of the image stays in the middle of the container whilst getting larger.
I have written some code for this but doesn't work, I expect my maths is wrong. Could anyone help?
I want it to work like this does. When you scroll into an image it keeps the image centered based on the current pan rather than zooming out from the corner.
HTML:
<div id="creator_container" style="position: relative; width: 300px; height: 400px; overflow: hidden;">
<img src="/images/test.gif" class="user_image" width="300" style="cursor: move; position: absolute; left: 0; top: 0;">
</div>
Javascript:
$("#_popup_creator .user_image").bind('mousewheel', function(event, delta) {
zoomPercentage += delta;
$(this).css('width',zoomPercentage+'%');
$(this).css('height',zoomPercentage+'%');
var widthOffset = (($(this).width() - $(this).parent().width()) / 2);
$(this).css('left', $(this).position().left - widthOffset);
});
Long story short, you need to make a transform matrix to scale by the same amount as the image and then transform the image's position using that matrix. If that explanation is complete greek to you, look up "image transforms" and "matrix math".
The beginning of this page is a pretty good resource to start with even though it's a different programming language:
http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/geom/Matrix.html
Anyway, I've implemented those methods in some projects of my own. Here's the zoom in function from something I wrote that functions the way you want:
function zoomIn(event) {
var prevS = scale;
scale += .1;
$(map).css({width: (baseSizeHor * scale) + "px", height: (baseSizeVer * scale) + "px"});
//scale from middle of screen
var point = new Vector.create([posX - $(viewer).width() / 2, posY - $(viewer).height() / 2, 1]);
var mat = Matrix.I(3);
mat = scaleMatrix(mat, scale / prevS, scale / prevS);
point = transformPoint(mat, point);
//http://stackoverflow.com/questions/1248081/get-the-browser-viewport-dimensions-with-javascript
posX = point.e(1) + $(viewer).width() / 2;
posY = point.e(2) + $(viewer).height() / 2;
$(map).css({left: posX, top: posY});
return false;//prevent drag image out of browser
}
Note the commands "new Vector.create()" and "Matrix.I(3)". Those come from the JavaScript vector/matrix math library http://sylvester.jcoglan.com/
Then note "transformPoint()". That's one of the functions from that ActionScript link (plus hints on http://wxs.ca/js3d/) that I implemented using sylvester.js
For the full set of functions I wrote:
function translateMatrix(mat, dx, dy) {
var m = Matrix.create([
[1,0,dx],
[0,1,dy],
[0,0,1]
]);
return m.multiply(mat);
}
function rotateMatrix(mat, rad) {
var c = Math.cos(rad);
var s = Math.sin(rad);
var m = Matrix.create([
[c,-s,0],
[s,c,0],
[0,0,1]
]);
return m.multiply(mat);
}
function scaleMatrix(mat, sx, sy) {
var m = Matrix.create([
[sx,0,0],
[0,sy,0],
[0,0,1]
]);
return m.multiply(mat);
}
function transformPoint(mat, vec) {
return mat.multiply(vec);
}