I am using paper.js to create a (non-interactive) display. I have fixed area inside which I want to show some text. When the text is large, I want to clip the text so as to show only the part inside the fixed area is visible to the user. In addition, in that case, I want to scroll the text left-to-right repeatedly.
As far as I could read in the paperjs documentation, there is no strait forward way to achieve this (.
So, I am thinking of doing the following:
Determine if the text is larger than the container box. Could not find an easy way to do this. Thinking of having a simple heuristic function - which goes by the number of characters in the text.
Clip the text within the container box. I have not figured out yet but it appears possible.
Change the anchor point gradually to the left to create the scrolling effect. Again to determine how far to go, we would need the size of the text - but may need to depend on a heuristic function again.
May be the upcoming feature AreaText would make doing this easier. Please let me know if there is a better way to do this in the meantime. Thanks.
Please let me know if there are samples in plain canvas or in another js framework ...
If PaperJS works well for your app but you need scrolling text, you could make a hybrid app that uses both a PaperJS canvas and a native html5 canvas. The native html5 canvas could just do the scrolling text.
Since you ask for ideas...Here's an autoscrolling text script that I did a while back.
You're welcome to use the code as a starting point for your scrolling script. It word-wraps sentences into paragraphs and then displays them on an auto-scrolling html canvas.
When you need to display some text you could temporarily overlay your PaperJS canvas with this scrolling text html5 canvas and remove the html5 canvas when done displaying the text. Its would also be easy to give the user control of the scrolling using an html input-range control.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var Paragraphs=( function(){
function Paragraphs(x,y,maxwidth,fontsize,fontface){
this.x=x;
this.y=y;
this.originalY=y;
this.maxwidth=maxwidth;
this.fontsize=fontsize;
this.fontface=fontface;
this.autoLineAdvance=true;
this.writeCount=0;
this.p=[];
this.nextTime=0;
this.duration=1000/60*1;
this.viewheight=150;
this.offsetY=0;
this.canvas=document.createElement('canvas');
this.ctx=this.canvas.getContext('2d');
this.totalHeight=0;
};
Paragraphs.prototype.addParagraph=function(text){ this.p.push(text); }
Paragraphs.prototype.autoscroll=function(viewportHeight){
this.viewheight=ch;
this.offsetY=ch;
requestAnimationFrame(this.animatescroll.bind(this));
};
Paragraphs.prototype.animatescroll=function(time){
var that=this;
if(this.offsetY>0){
requestAnimationFrame(that.animatescroll.bind(that));
}else{log('done');}
if(time<this.nextTime){return;}
this.nextTime=time+this.duration;
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(this.canvas,this.x,this.offsetY);
this.offsetY-=0.50;
};
Paragraphs.prototype.lineAdvance=function(){this.y+=this.fontsize*1.286*1.5};
Paragraphs.prototype.drawOffCanvas=function(){
var y=0;
var lineCount=0;
var lineHeight = this.fontsize*1.286;
var lineAdvance=lineHeight*1.5;
this.canvas.width=this.maxwidth;
this.canvas.height=this.height();
this.ctx.textBaseline='top';
this.ctx.font = this.fontsize + "px " + this.fontface;
for(var i=0;i<this.p.length;i++){
var words=this.p[i].split(' ');
var line = '';
var space='';
for (var n=0; n<words.length; n++) {
var testLine = line + space + words[n];
space=' ';
if (this.ctx.measureText(testLine).width > this.maxwidth) {
this.ctx.fillText(line, 1, y);
line = words[n] + ' ';
y += lineHeight;
space='';
} else {
line = testLine;
}
}
this.ctx.fillText(line, 1,y);
y+=lineAdvance;
}
};
Paragraphs.prototype.height=function(){
ctx.save();
ctx.textBaseline='top';
ctx.font = this.fontsize + "px " + this.fontface;
var lineHeight = this.fontsize*1.286;
var writeCount=0;
var height=lineHeight*(this.p.length-1)*0.50;
var line,space;
for(var i=0;i<this.p.length;i++){
var words=this.p[i].split(' ');
line=space='';
if((writeCount++)>0){ height+=lineHeight; }
for (var n=0; n<words.length; n++) {
var testLine = line + space + words[n];
space=' ';
if (ctx.measureText(testLine).width > this.maxwidth) {
line = words[n] + ' ';
height+=lineHeight;
space='';
} else {
line = testLine;
}
}
}
height+=lineHeight;
ctx.restore();
this.totalHeight=height;
return(height);
}
return(Paragraphs);
})();
var d=new Paragraphs(35,20,200,14,'verdana');
d.addParagraph("I am using paper.js to create a (non-interactive) display. I have fixed area inside which I want to show some text. When the text is large, I want to clip the text so as to show only the part inside the fixed area is visible to the user. In addition, in that case, I want to scroll the text left-to-right repeatedly.");
d.addParagraph("Please let me know if there are samples in plain canvas or in another js framework ...");
d.drawOffCanvas();
d.autoscroll();
body{ background-color: ivory; padding:10px; }
canvas{border:1px solid red;}
<h4>Text scrolls in from the bottom<br>Be patient or click full page mode</h4>
<canvas id="canvas" width=300 height=300></canvas>
Related
I want to create a simple jsp / servlet code which should have the following:
1) Display an image having different sections. For example: a country map.
2) Allow the user to mark sections on this image using mouse drag. As the user keeps dragging the mouse, the area gets overlay-ed with some different color.
3) As the user moves the mouse, the x and y coordinates on the image should also get calculated. [This feature is however optional.]
The purpose of this application is to mark different 'zones' in an image, which will get saved in the database along with their x-y coordinates.
Can someone please suggest how to achieve this ? Is there any library / API available which could be helpful ?
Regards,
Here is a snippet to get you going. Replace the text setting of the div#server with code to send coordinates to your server. I'll leave the background image for canvas and other important stuff up to you.
var c = $("#map");
var ctx = c[0].getContext("2d");
var down = [0, 0];
var bound = c[0].getBoundingClientRect();
c.mousedown(function(e) {
down = [e.clientX - bound.left, e.clientY - bound.top];
});
c.mouseup(function(e) {
var rect = [
down[0],
down[1], ((e.clientX - bound.left) - down[0]), ((e.clientY - bound.top) - down[1])
];
ctx.fillStyle = "#bbbbbb";
ctx.fillRect(rect[0], rect[1], rect[2], rect[3]);
$("#server").text("Send: " + rect);
});
body {
background-color: lightblue;
}
#map {
background-color: white;
cursor: crosshair;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas id="map" width="256" height="256"></canvas>
<div id="server"></div>
I have JavaScript code in my site.
The code is using 2 identical photos-same size, same resolution - only different colors.
First photo is black and white photo - this is what the canvas presents.
Second photo is the same only with the original colors.
I have a button that triggers JS code - which generally removes a pixel from the black and white -and paints color pixel on the canvas. At first I used Math.random for the pixel locations.
And than I decided to use it by order. No matter where it starts or begging.. as long it will go
in this order (x,y)..(x+1,y).. until maximum x.. and than (x,y+1).. until maximum x.. and so on until all the black and white photo "transformed" into the colorful photo..
for some reason I just cant make it happen.. i tried a lot of techniques..
here is demo for global understanding:
demo is working sorry - they deactivated my free host :\ hope you still understand..
here is the original code- i just changed the last function : **removeDrawRandomPixel** ..it's just playing the function there and it should be fixed..
///////////////////////global variables///////////////////
var gray_url="bwcat.jpg"; //black and white image URI
var regular_url="cat.jpg"; //regular image URI
var n=100; //number of pixels changed per click
/////////////////////////////////////
document.addEventListener("DOMContentLoaded", function(){
var c=new EditableCanvas(document.getElementById('cnvs'));
grayScaleImage=new Image();
grayScaleImage.src=gray_url;
grayScaleImage.onload=function()
{
c.drawImage(this);
}
regularImage=new Image();
regularImage.src=regular_url;
regularImage.onload=function()
{
var p=getPixelArray(this);
btn.onclick=function(){
for(var i=1;i<=n&&p.length>0;i++){
removeDrawRandomPixel(p,c);
}
}
}
},false);
//create a Pixel object
function ImagePixel(x,y,r,g,b,a)
{
this.x=x;
this.y=y;
this.r=r;
this.g=g;
this.b=b;
this.a=a;
}
//object that allows custom methods
function EditableCanvas(canvas)
{
this.canvas=canvas;
this.context=canvas.getContext('2d');
this.width=canvas.width;
this.height=canvas.height;
this.pixelImage=this.context.createImageData(1,1);
//draw an entire picture and adjust the canvas for it
this.drawImage=function(image)
{
this.width=image.width;
this.height=image.height;
this.canvas.height=image.height;
this.canvas.width=image.width;
this.context.drawImage(image,0,0);
}
//draw a single pixel, ImagePixel pixel
this.drawPixel=function(pixel)
{
this.pixelImage.data[0]=pixel.r;
this.pixelImage.data[1]=pixel.g;
this.pixelImage.data[2]=pixel.b;
this.pixelImage.data[3]=pixel.a;
this.context.putImageData(this.pixelImage,pixel.x,pixel.y);//,pixel.x,pixel.y);
}
}
//the function returns an ordered array of Pixel pixels of the image at `src`.
function getPixelArray(img)
{
var pixelArray=[];
var cnvs=document.createElement('canvas');
cnvs.width=img.width;
cnvs.height=img.width;
var context=cnvs.getContext('2d');
context.drawImage(img,0,0);
var originalData = context.getImageData(0,0,img.width,img.height).data;
for(var i=0,pixelId=0,px;i<originalData.length;i+=4)
{
px=new ImagePixel(pixelId%img.width,Math.floor(pixelId/img.width),originalData[i],originalData[i+1],originalData[i+2],originalData[i+3]);
pixelArray.push(px);
pixelId++;
}
return pixelArray;
}
//the function remove a random pixel from pixelArray and draws it on editableCnvs
function removeDrawRandomPixel(pixelArray,editableCnvs)
{
var place=Math.floor(Math.random()*pixelArray.length);
var px=pixelArray.splice(place,1)[0];
editableCnvs.drawPixel(px);
}
html :
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>canvas rules</title>
<script src="pixel.js"></script>
</head>
<body>
<canvas id="cnvs">
oh goddmamit no support
</canvas>
<button id="btn">click to convert</button>
</body>
</html>
I tried playing the last function.. because i know the answer is in the function,how to choose the pixels..
This is untested however this is what I would change the removeDrawRandomPixel function to. You are grabbing a random point in the array currently and passing it, so it could be starting on the blue component of one pixel and ending on the g component of another pixel.
//the function remove a random pixel from pixelArray and draws it on editableCnvs
function removeDrawRandomPixel(pixelArray,editableCnvs)
{
// width and height need to be the width and height of the canvas.
var width = canvas.width,
height = canvas.height,
x = Math.floor(Math.random()*width),
y = Math.floo(Math.random()*height);
var px = (y * width + x) * 4;
editableCnvs.drawPixel(px);
}
I'm trying to recreate a Snapchat "snap" with HTML5 Canvas. The part that has me stumped is getting the font to look the same. The font Snapchat uses seems to be Helvetica, but I can't get my text to look the same as that of a real snap.
Here's my attempt of matching the font. I've drawn my text on top of an actual screenshot of a snap so it's easy to compare. You can edit the JavaScript variables to try to match the text (JSFiddle: http://jsfiddle.net/9RB88/1/).
HTML:
<img src='http://i.imgur.com/tivQ8xJ.jpg'>
<canvas width='640' height='1136'></canvas>
CSS:
img {display:none}
JavaScript:
//--- EDIT THESE ----------------
// text styles...
var fontFamily = 'Helvetica';
var fontSize = '35px';
var fontWeight = 'normal';
// http://css-tricks.com/almanac/properties/f/font-weight/
var onTop = false;
// set this to "true" to place our text on top of the real text (for more precise comparing)
var differentColours = false;
// draw our text/background different colours, making it easier to compare (use when "onTop" is "true")
// tweak where the text is drawn
var horOffset = 2;
var vertOffset = 0;
//-------------------------------
var can = document.getElementsByTagName('canvas')[0];
var c = can.getContext('2d');
c.fillStyle = 'white';
c.fillRect(0, 0, can.width, can.height);
var img = document.getElementsByTagName('img')[0];
c.drawImage(img, 0, 0);
var top;
if(onTop) top = 531;
else top = 450;
if(differentColours) c.fillStyle = 'orange';
else c.fillStyle = 'black';
c.globalAlpha = 0.6;
c.fillRect(0, top, can.width, 74);
c.globalAlpha = 1;
var text = 'Template example thingy $&#?! 123';
if(differentColours) c.fillStyle = 'red';
else c.fillStyle = 'white';
c.textAlign = 'center';
c.font = fontWeight + ' ' + fontSize + ' ' + fontFamily;
c.fillText(text, can.width/2 + horOffset, top + 50 + vertOffset);
JSFiddle: http://jsfiddle.net/9RB88/1/
It seems like one of the issues is letter spacing, but that's not possible to change in HTML5 Canvas.
What can I do?
Snapchat may embed a very specific variant or similar font to Helvetica. That doesn't mean the user loading the page (incl. on your own machine) will have that font installed on the system.
If the user doesn't have the font installed the browser will look for similar fonts, for example, it will use Arial on Windows and if that is not available use another sans-serif font. On Mac and Linux the same thing. This mean it will perhaps closely match but will not be the same font. If they don't use a web font the snap could have been taken on a different OS then you are using resulting in a different font being rendered.
Or in other words - when you specify "Helvetica" as font family the system will try to find a font in that family but it isn't given that Helvetica is the font you'll get. The browser will try to find the closest matching font available on the system in use.
You have to embed the very same font they are using as a web font otherwise there is no guarantee that the font will be the same.
To find out which font they are using (if any) open the stylesheets they're using.
My 2 cents..
I have the following div markup
<div data-x="-1000" data-y="-1500">
// content
</div>
<div data-x="0" data-y="-1500">
// content
</div>
And I have many of this divs with different data-x and data-y value depending on their position.
What I want to achieve here is to draw something like a timeline between the divs so div1 line to div2 line to div 3 etc,,
I want this to be done automatically So I am trying to make a loop for it but my javascript/jquery knowledge is not that good. Could someone point me in the good direction?
what I have now is
function drawTimeline() {
var divs = document.getElementsByTagName('div');
var canvas = document.getElementById('timeline');
// Make sure we don't execute when canvas isn't supported
if (canvas.getContext){
// use getContext to use the canvas for drawing
var ctx = canvas.getContext('2d');
var prevCoord = {};
for (var i = -1; div = divs[++i]; ) {
if (div.dataset.x && div.dataset.y) {
var x = parseInt(div.dataset.x);
var y = parseInt(div.dataset.y);
if ({} !== prevCoord) {
ctx.beginPath();
ctx.lineWidth="5";
ctx.strokeStyle="purple"; // Purple path
ctx.moveTo(prevCoord.x, prevCoord.y);
ctx.lineTo(x, y);
ctx.closePath();
ctx.stroke()
}
prevCoord.x = x;
prevCoord.y = y;
}
}
} else {
alert('You need Safari or Firefox 1.5+ to see this.');
}
}
unfortunately the line is not correct its like a linear line and thats it.. can someone help me out with this?
You have the right idea, but your problem is that canvas does not draw at negative coordinates.
Therefore, you must map all your data-x and data-y into positive coordinates.
Here's a function that maps your negative values into positive values:
function mapRange(value, sourceLow, sourceHigh, mappedLow, mappedHigh) {
return mappedLow + (mappedHigh - mappedLow) * (value - sourceLow) / (sourceHigh - sourceLow);
}
So in your example to map a data-x value of -300 into an onscreen range of 0-1000 can do this:
var x = mapRange(-300, -1500,0, 0,1000); // The mapped x is 466.66.
You must reposition your divs to the mapped x,y coordinates which you can do with CSS.
An alternative is to use SVG which creates actual DOM elements that can be positioned at negative coordinates.
I have the following code in the index.html page of my site, which when the page loads, draws a number of images to the HTML5 canvas:
window.onload = function(){
var sources = {};
sources[0] = document.getElementById("building").src,
sources[1] = document.getElementById("chair").src,
sources[2] = document.getElementById("drink").src,
sources[3] = document.getElementById("food").src,
sources[4] = document.getElementById("fridge").src,
sources[5] = document.getElementById("land").src,
sources[6] = document.getElementById("money").src,
sources[7] = document.getElementById("oven").src,
sources[8] = document.getElementById("table").src,
sources[9] = document.getElementById("van").src,
sources[10] = document.getElementById("burger").src,
sources[11] = document.getElementById("chips").src,
sources[12] = document.getElementById("drink").src,
sources[13] = document.getElementById("franchiseFee").src,
sources[14] = document.getElementById("wages").src,
sources[15] = document.getElementById("admin").src,
sources[16] = document.getElementById("cleaners").src,
sources[17] = document.getElementById("electricity").src,
sources[18] = document.getElementById("insurance").src,
sources[19] = document.getElementById("manager").src,
sources[20] = document.getElementById("rates").src,
sources[21] = document.getElementById("training").src,
sources[22] = document.getElementById("water").src,
sources[23] = document.getElementById("burger").src,
sources[24] = document.getElementById("chips").src,
sources[25] = document.getElementById("drink").src,
sources[26] = document.getElementById("creditors").src,
sources[27] = document.getElementById("electricity").src,
sources[28] = document.getElementById("food").src,
sources[29] = document.getElementById("hirePurchase").src,
sources[30] = document.getElementById("loan").src,
sources[31] = document.getElementById("overdraft").src,
sources[32] = document.getElementById("payeTax").src,
sources[33] = document.getElementById("tax").src
loadImages(sources, drawImage);
};
Sources is the array that I'm using to hold the images in JavaScript so that they can be drawn to the canvas once they've been loaded from a hidden section in my HTML.
This function currently works exactly as it's intended- it has a call to the loadImages function, which loads the images from a hidden section in the HTML into the JavaScript array, and calls the drawImage function on each of the images in the array.
But I also have another function that I want to be called with the window.onload:
The function I want to add to window.onload is this:
function drawGameElements(){
/* Draw a line for the 'score bar'. */
context.moveTo(0, 25);
context.lineTo(1000, 25);
context.stroke();
/* Draw current level/ total levels on the left, and current score on the right. */
context.font = "11pt Calibri"; /* Text font & size */
context.strokeStyle = "black"; /* Font colour */
context.strokeText(currentLevel + "/" + totalLevels, 10, 15);
context.strokeText(currentScore, 950, 15);
}
I tried adding a call to the function just below the loadImages(sources, drawImage); line in window.onload = function(){};
So that I now have:
window.onload = function(){
...
loadImages(sources, drawImage);
drawGameElements();
};
Although this partially works, in that it draws the line across the top of the canvas for the 'score bar' and writes "1/3" for the levels on the left hand side of the canvas just above the line, for some reason it doesn't draw the current score on the right hand side.
Also, as soon as I click on one of the images that's been drawn to the canvas, to drag and drop it around the canvas, the 'score bar' then disappears from the canvas completely.
Does anyone know why this is? How can I get the score bar to remain visible throughout the duration of the game, no matter what else happens on the canvas? Also, how can I get the currentScore variable to be displayed?
I would instead use a callback inside of loadImages to call drawGameElements. That way drawGameElements only runs after loadImages is finished. It sounds like you've created a race condition with your two functions.
If I where you, I would create the score bar outside of the canvas. Set its position to absolute and let it hover above the canvas at the desired location. That way you can just access it as HTML elements and do not need to worry about redrawing.
The major advantage of using HTML elements for these displays is that you don't need to redraw them, for example after moving the images around on the canvas. You also don't have to worry about how to refresh the values (on a canvas, just painting new values over the old ones will not be enough). This way you can just store the values in HTML elements and go wild on the canvas, knowing the score will always stay on top.