How to wrap text in Node canvas? [duplicate] - javascript

I am trying to add text on an image using the <canvas> element. First the image is drawn and on the image the text is drawn. So far so good.
But where I am facing a problem is that if the text is too long, it gets cut off in the start and end by the canvas. I don't plan to resize the canvas, but I was wondering how to wrap the long text into multiple lines so that all of it gets displayed. Can anyone point me at the right direction?

Updated version of #mizar's answer, with one severe and one minor bug fixed.
function getLines(ctx, text, maxWidth) {
var words = text.split(" ");
var lines = [];
var currentLine = words[0];
for (var i = 1; i < words.length; i++) {
var word = words[i];
var width = ctx.measureText(currentLine + " " + word).width;
if (width < maxWidth) {
currentLine += " " + word;
} else {
lines.push(currentLine);
currentLine = word;
}
}
lines.push(currentLine);
return lines;
}
We've been using this code for some time, but today we were trying to figure out why some text wasn't drawing, and we found a bug!
It turns out that if you give a single word (without any spaces) to the getLines() function, it will return an empty array, rather than an array with a single line.
While we were investigating that, we found another (much more subtle) bug, where lines can end up slightly longer than they should be, since the original code didn't account for spaces when measuring the length of a line.
Our updated version, which works for everything we've thrown at it, is above. Let me know if you find any bugs!

A possible method (not completely tested, but as for now it worked perfectly)
/**
* Divide an entire phrase in an array of phrases, all with the max pixel length given.
* The words are initially separated by the space char.
* #param phrase
* #param length
* #return
*/
function getLines(ctx,phrase,maxPxLength,textStyle) {
var wa=phrase.split(" "),
phraseArray=[],
lastPhrase=wa[0],
measure=0,
splitChar=" ";
if (wa.length <= 1) {
return wa
}
ctx.font = textStyle;
for (var i=1;i<wa.length;i++) {
var w=wa[i];
measure=ctx.measureText(lastPhrase+splitChar+w).width;
if (measure<maxPxLength) {
lastPhrase+=(splitChar+w);
} else {
phraseArray.push(lastPhrase);
lastPhrase=w;
}
if (i===wa.length-1) {
phraseArray.push(lastPhrase);
break;
}
}
return phraseArray;
}

Here was my spin on it... I read #mizar's answer and made some alterations to it... and with a little assistance I Was able to get this.
code removed, see fiddle.
Here is example usage. http://jsfiddle.net/9PvMU/1/ - this script can also be seen here and ended up being what I used in the end... this function assumes ctx is available in the parent scope... if not you can always pass it in.
edit
the post was old and had my version of the function that I was still tinkering with. This version seems to have met my needs thus far and I hope it can help anyone else.
edit
It was brought to my attention there was a small bug in this code. It took me some time to get around to fixing it but here it is updated. I have tested it myself and it seems to work as expected now.
function fragmentText(text, maxWidth) {
var words = text.split(' '),
lines = [],
line = "";
if (ctx.measureText(text).width < maxWidth) {
return [text];
}
while (words.length > 0) {
var split = false;
while (ctx.measureText(words[0]).width >= maxWidth) {
var tmp = words[0];
words[0] = tmp.slice(0, -1);
if (!split) {
split = true;
words.splice(1, 0, tmp.slice(-1));
} else {
words[1] = tmp.slice(-1) + words[1];
}
}
if (ctx.measureText(line + words[0]).width < maxWidth) {
line += words.shift() + " ";
} else {
lines.push(line);
line = "";
}
if (words.length === 0) {
lines.push(line);
}
}
return lines;
}

context.measureText(text).width is what you're looking for...

Try this script to wrap the text on a canvas.
<script>
function wrapText(ctx, text, x, y, maxWidth, lineHeight) {
var words = text.split(' ');
var line = '';
for(var n = 0; n < words.length; n++) {
var testLine = line + words[n] + ' ';
var metrics = ctx.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) {
ctx.fillText(line, x, y);
line = words[n] + ' ';
y += lineHeight;
}
else {
line = testLine;
}
}
ctx.fillText(line, x, y);
}
var canvas = document.getElementById('Canvas01');
var ctx = canvas.getContext('2d');
var maxWidth = 400;
var lineHeight = 24;
var x = (canvas.width - maxWidth) / 2;
var y = 70;
var text = 'HTML is the language for describing the structure of Web pages. HTML stands for HyperText Markup Language. Web pages consist of markup tags and plain text. HTML is written in the form of HTML elements consisting of tags enclosed in angle brackets (like <html>). HTML tags most commonly come in pairs like <h1> and </h1>, although some tags represent empty elements and so are unpaired, for example <img>..';
ctx.font = '15pt Calibri';
ctx.fillStyle = '#555555';
wrapText(ctx, text, x, y, maxWidth, lineHeight);
</script>
</body>
See demo here http://codetutorial.com/examples-canvas/canvas-examples-text-wrap.

From the script here: http://www.html5canvastutorials.com/tutorials/html5-canvas-wrap-text-tutorial/
I've extended to include paragraph support. Use \n for new line.
function wrapText(context, text, x, y, line_width, line_height)
{
var line = '';
var paragraphs = text.split('\n');
for (var i = 0; i < paragraphs.length; i++)
{
var words = paragraphs[i].split(' ');
for (var n = 0; n < words.length; n++)
{
var testLine = line + words[n] + ' ';
var metrics = context.measureText(testLine);
var testWidth = metrics.width;
if (testWidth > line_width && n > 0)
{
context.fillText(line, x, y);
line = words[n] + ' ';
y += line_height;
}
else
{
line = testLine;
}
}
context.fillText(line, x, y);
y += line_height;
line = '';
}
}
Text can be formatted like so:
var text =
[
"Paragraph 1.",
"\n\n",
"Paragraph 2."
].join("");
Use:
wrapText(context, text, x, y, line_width, line_height);
in place of
context.fillText(text, x, y);

I am posting my own version used here since answers here weren't sufficient for me. The first word needed to be measured in my case, to be able to deny too long words from small canvas areas. And I needed support for 'break+space, 'space+break' or double-break/paragraph-break combos.
wrapLines: function(ctx, text, maxWidth) {
var lines = [],
words = text.replace(/\n\n/g,' ` ').replace(/(\n\s|\s\n)/g,'\r')
.replace(/\s\s/g,' ').replace('`',' ').replace(/(\r|\n)/g,' '+' ').split(' '),
space = ctx.measureText(' ').width,
width = 0,
line = '',
word = '',
len = words.length,
w = 0,
i;
for (i = 0; i < len; i++) {
word = words[i];
w = word ? ctx.measureText(word).width : 0;
if (w) {
width = width + space + w;
}
if (w > maxWidth) {
return [];
} else if (w && width < maxWidth) {
line += (i ? ' ' : '') + word;
} else {
!i || lines.push(line !== '' ? line.trim() : '');
line = word;
width = w;
}
}
if (len !== i || line !== '') {
lines.push(line);
}
return lines;
}
It supports any variants of lines breaks, or paragraph breaks, removes double spaces, as well as leading or trailing paragraph breaks. It returns either an empty array if the text doesn't fit. Or an array of lines ready to draw.

look at https://developer.mozilla.org/en/Drawing_text_using_a_canvas#measureText%28%29
If you can see the selected text, and see its wider than your canvas, you can remove words, until the text is short enough. With the removed words, you can start at the second line and do the same.
Of course, this will not be very efficient, so you can improve it by not removing one word, but multiple words if you see the text is much wider than the canvas width.
I did not research, but maybe their are even javascript libraries that do this for you

I modified it using the code from here http://miteshmaheta.blogspot.sg/2012/07/html5-wrap-text-in-canvas.html
http://jsfiddle.net/wizztjh/kDy2U/41/

This should bring the lines correctly from the textbox:-
function fragmentText(text, maxWidth) {
var lines = text.split("\n");
var fittingLines = [];
for (var i = 0; i < lines.length; i++) {
if (canvasContext.measureText(lines[i]).width <= maxWidth) {
fittingLines.push(lines[i]);
}
else {
var tmp = lines[i];
while (canvasContext.measureText(tmp).width > maxWidth) {
tmp = tmp.slice(0, tmp.length - 1);
}
if (tmp.length >= 1) {
var regex = new RegExp(".{1," + tmp.length + "}", "g");
var thisLineSplitted = lines[i].match(regex);
for (var j = 0; j < thisLineSplitted.length; j++) {
fittingLines.push(thisLineSplitted[j]);
}
}
}
}
return fittingLines;
And then get draw the fetched lines on the canvas :-
var lines = fragmentText(textBoxText, (rect.w - 10)); //rect.w = canvas width, rect.h = canvas height
for (var showLines = 0; showLines < lines.length; showLines++) { // do not show lines that go beyond the height
if ((showLines * resultFont.height) >= (rect.h - 10)) { // of the canvas
break;
}
}
for (var i = 1; i <= showLines; i++) {
canvasContext.fillText(lines[i-1], rect.clientX +5 , rect.clientY + 10 + (i * (resultFont.height))); // resultfont = get the font height using some sort of calculation
}

This is a typescript version of #JBelfort's answer.
(By the way, thanks for this brilliant code)
As he mentioned in his answer this code can simulate html element such as textarea,and also the CSS property
word-break: break-all
I added canvas location parameters (x, y and lineHeight)
function wrapText(
ctx: CanvasRenderingContext2D,
text: string,
maxWidth: number,
x: number,
y: number,
lineHeight: number
) {
const xOffset = x;
let yOffset = y;
const lines = text.split('\n');
const fittingLines: [string, number, number][] = [];
for (let i = 0; i < lines.length; i++) {
if (ctx.measureText(lines[i]).width <= maxWidth) {
fittingLines.push([lines[i], xOffset, yOffset]);
yOffset += lineHeight;
} else {
let tmp = lines[i];
while (ctx.measureText(tmp).width > maxWidth) {
tmp = tmp.slice(0, tmp.length - 1);
}
if (tmp.length >= 1) {
const regex = new RegExp(`.{1,${tmp.length}}`, 'g');
const thisLineSplitted = lines[i].match(regex);
for (let j = 0; j < thisLineSplitted!.length; j++) {
fittingLines.push([thisLineSplitted![j], xOffset, yOffset]);
yOffset += lineHeight;
}
}
}
}
return fittingLines;
}
and you can just use this like
const wrappedText = wrapText(ctx, dialog, 200, 100, 200, 50);
wrappedText.forEach(function (text) {
ctx.fillText(...text);
});
}

Related

updatePixels() not actually updating the pixels

I'm trying to make a program that takes a string, converts it to base64, and then to binary. It then takes the binary and changes the pixels a black pixel for 0 and a white pixel for 1.
I've gotten the pixel array to change to what I want, but it's not actually changing when I call updatePixels().
My goal is to then take the canvas and export it as an image.
My sketch:
let hw;
let input, button;
let binaryOut;
function setup() {
createCanvas(140,140);
input=createInput();
pixelDensity(1);
button = createButton("get image");
button.mousePressed(txtTo64ToBin)
loadPixels();
}
function txtTo64ToBin(){
str = input.value();
str = btoa(str);
let output = '';
str = str.split("")
for(let i=0;i<str.length;i++){
let base = str[i].charCodeAt(0).toString(2)
while(base.length < 8){
base = "0"+base;
}
output += base;
}
binaryOut = output;
console.log(binaryOut)
updateImage(binaryOut.split(''))
}
function updateImage(binArr){
hw = factors(binArr.length);
hw = hw[hw.length-1];
console.log(hw);
resizeCanvas(...hw,false)
pixels = []
for(let i=0; i<binArr.length; i++){
pixels[i*4] = map(binArr[i],0,1,0,255);
pixels[i*4+1] = map(binArr[i],0,1,0,255);
pixels[i*4+2] = map(binArr[i],0,1,0,255);
pixels[i*4+3] = 255;
}
console.log(pixels)
updatePixels() //here is the updatePixels function call
}
function draw() {
noLoop();
}
function factors(num) {
var half = Math.floor(num / 2),
arr = [],
i, j;
num % 2 === 0 ? (i = 2, j = 1) : (i = 3, j = 2);
for (i; i <= half; i += j) {
if(num % i === 0 && i <= num/i){
arr.push([i,num/i]);
}
}
return arr;
}
I'm very confused and any help would be much appreciated.
Please try to break your problem down into smaller steps and isolate the problem in a smaller example.
Here is an example sketch that shows the same problem:
let button;
function setup() {
createCanvas(140,140);
button = createButton("test");
button.mousePressed(updateImage);
loadPixels();
}
function updateImage(){
pixels = [];
for(let i=0; i < width * height; i++){
pixels[i*4] = 255;
pixels[i*4+1] = 0;
pixels[i*4+2] = 0;
pixels[i*4+3] = 255;
}
updatePixels();
}
function draw() {
noLoop();
}
We might expect this to turn the canvas red when we click the button, but it does not. See how this example is easier to play with, because we don't have to think about any of your logic?
Anyway, the problem is caused by this line:
pixels = [];
Take that line out, and the example program works.
My guess is this is because pixels is not a standard JavaScript array. From the reference:
Uint8ClampedArray containing the values for all the pixels in the display window.
...
Note that this is not a standard javascript array.

Failed JS Mandelbrot Set generator outputs odd structure

I made this simple Mandelbrot Set generator in Javascript last night, but it outputs a really strange structure. I think it looks similar to the mandelbrot set, yet oddly deformed. I have no idea why it distorts like this, and I've been trying to find out all day. Does anyone know what causes this or how to fix this?
c = document.getElementById("canvas");
ctx = c.getContext("2d");
c.width = 4000;
c.height = 4000;
declareVariables();
calculateShape();
drawShape();
function declareVariables() {
Re = -2;
Im = -2;
input = [Re,Im];
precision = prompt("input precision (higher is better)");
precision = 1/(precision - precision%4);
segmentAmt = 4/precision;
segmentSize = c.width/segmentAmt;
iterate = prompt("input test amount (higher is better)");
set = [];
for (i=0; i<segmentAmt; i++) {
set[i] = [];
}
numberGrid = [];
for (i=0; i<segmentAmt; i++) {
numberGrid[i] = [];
for (j=0; j<segmentAmt; j++) {
}
}
}
function calculateShape() {
for (i=0; i<segmentAmt; i++) {
input[1] = -2;
input[0] += precision;
for (j=0; j<segmentAmt; j++) {
input[1] += precision;
set[i][j] = 0;
z = [0,0];
for (k=1; k<=iterate; k++) {
store = z;
z[0] = store[0]**2 - store[1]**2 + input[0];
z[1] = 2 * store[0] * store[1] + input[1];
if (z[0]**2 + z[1]**2 > 4) {
set[i][j] = k;
k = iterate+1;
}
}
}
}
}
function drawShape() {
ctx.fillStyle = "white";
ctx.fillRect(0,0,c.width,c.height);
for (i=0; i<segmentAmt; i++) {
for (j=0; j<segmentAmt; j++) {
if (set[i][j] == 0) {
ctx.fillStyle = "black";
} else if (set[i][j] >= 1) {
ctx.fillStyle = 'hsl(' + (25*(set[i][j]-1))**0.75 + ', 100%, 50%)';
}
convertCoords(i,j);
ctx.fillRect(xCoord,yCoord,segmentSize,segmentSize);
}
}
}
function convertCoords(var1,var2) {
xCoord = var1 * segmentSize;
yCoord = var2 * segmentSize;
}
Output image:
The error appears to be on this line in calculateShape():
store = z;
It seems you want store to be a copy of z, but this just ends up with store and z referring to the same array. The next line calculates z[0], but as store and z refer to the same array, store[0] has the new value of z[0] rather than the previous. Hence the calculation of z[1] in the line after that is incorrect.
Replace the above line with either
store = [z[0], z[1]];
or
store = z.slice();
Both of these lines ensure that store refers to a different array to z, so when you recalculate z[0], store[0] is unaffected.

Javascript toLowerCase strange behaviour

I have a small application that reads tweets and tries to match keywords and I noticed this strange behaviour with a particular string:
var text = "The Νіk​е D​un​k​ Ніgh ЅΒ 'Uglу Ѕwеаt​еr​' іѕ n​оw аvаіlаblе http://swoo.sh/IHVaTL";
var lowercase = text.toLowerCase()
Now the value of lowercase is:
the νіk​е d​un​k​ ніgh ѕβ 'uglу ѕwеаt​еr​' іѕ n​оw аvаіlаblе
http://swoo.sh/ihvatl
So it seems like the string is in a weird format, I double checked some of the letters and found that:
text.charAt(4)
>"N"
text.charCodeAt(5)
>925
'N'.charCodeAt(0)
>78
So even if it looks like a normal N, the unicode associated to it corresponds to
0925 थ DEVANAGARI LETTER THA
according to the unicode chart
So I´m a bit puzzled about how this can happen, and if there is anyway to "convert" to the supposed real letter
There is a python library called unidecode that I've used to solve this problem in python before, it basically "flattens" unicode into ascii.
A quick google reveals that a similar library is available for JavaScript.
You can create a separate canvas with each Latin letter, upper case and lower case, to compare against. Each time you encounter a character that's not in the Latin-1 range, create a new canvas for it, and compare it against each Latin alphabet character using an image diff algorithm. Replace the non-Latin character with the closest match.
For example:
var latinize = (function () {
var latinLetters = [],
canvases = [],
size = 16,
halfSize = size >> 1;
function makeCanvas(chr) {
var canvas = document.createElement('canvas'),
context = canvas.getContext('2d');
canvas.width = size;
canvas.height = size;
context.textBaseline = 'middle';
context.textAlign = 'center';
context.font = (halfSize) + "px sans-serif";
context.fillText(chr, halfSize, halfSize);
return context;
}
function nextChar(chr) {
return String.fromCharCode(chr.charCodeAt(0) + 1);
}
function setupRange(from, to) {
for (var chr = from; chr <= to; chr = nextChar(chr)) {
latinLetters.push(chr);
canvases.push(makeCanvas(chr));
}
}
function calcDistance(ctxA, ctxB) {
var distance = 0,
dataA = ctxA.getImageData(0, 0, size, size).data,
dataB = ctxB.getImageData(0, 0, size, size).data;
for (var i = dataA.length; i--;) {
distance += Math.abs(dataA[i] - dataB[i]);
}
return distance;
}
setupRange('a', 'z');
setupRange('A', 'Z');
setupRange('', ''); // ignore blank characters
return function (text) {
var result = "",
scores, canvas;
for (var i = 0; i < text.length; i++) {
if (text.charCodeAt(i) < 128) {
result += text.charAt(i);
continue;
}
scores = [];
canvas = makeCanvas(text.charAt(i));
for (var j = 0; j < canvases.length; j++) {
scores.push({
glyph: latinLetters[j],
score: calcDistance(canvas, canvases[j])
});
}
scores.sort(function (a, b) {
return a.score - b.score;
});
result += scores[0].glyph;
}
return result;
}
}());
This translates your test string to "the nike dunk high sb 'ugly sweater' is now available".
The alternative is to create a giant data structure mapping all of the look-alike characters to their Latin-1 equivalents, as the library in #willy's answer does. This is extremely heavy for "browser JavaScript", and probably not suitable for sending to the client, as you can see by looking at the source for that project.
http://jsfiddle.net/Ly5Lt/4/

Collision code isn't working?

I can't understand collision. I know the idea behind it but it is so hard to picture, and I try to write it down but it doesn't work. This is the farthest I've gotten, and while it works, it isn't 100% flawless.
It is a square that moves around. Picture mario, but a square. I am trying to see if theres a block to the left, right, below, or above.
function collide(){
var left = avatarX;
var top = avatarY;
var right = avatarX + 50;
var bottom = avatarY + 50;
for(var i = 0; i < board.length;i+=4){
var bleft = board[i];
var btop = board[i+1];
var bright = board[i + 2] + board[i];
var bbottom = board[i + 3] + board[i+1];
if(btop <= bottom && bleft <= left && bright >= right){
avatarY = board[i + 1] - 50;
return true;
}
}
return false;
}
Your code is out of context and it's hard to know exactly what you are trying to do. If you are using a grid, I would say that you should check the spaces adjacent to the object you are detecting collisions for. If you post more code, perhaps I can help more.
var size = 5;//whatever you want
var x = new Array(size);
for (var i = 0; i < 10; i++) {
x[i] = new Array(size);
}

How to make matrix like text change effect in javascript?

I was trying to make this text change matrix movie like effect in JavaScript.The basic concept was that there is a div present in html and JavaScript take that div's inner text and manipulate it to create the continuously changing random text effect like in matrix movie. I am quite new to JavaScript, I am having hard time figuring out the logic behind the animation like the each steps, one step after another like what will happen next in whole animation process.
Anyways, I tried to make it on my own but as you can suspect i failed.
Here is my code :
<html>
<head>
<script>
var text = document.getElementById("text").innerText;
var length_var = text.length;
var possible = [];
var possible_text ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var counter = 0;
var arr_text = [];
var new_func = function(){
arr_text[Math.floor(Math.random() * length_var)] = possible.charAt(Math.floor(Math.random() * possible.length));
alert(arr_text);
};
var call_func = function(){
for(var i=0; i<=possible_text.length; i++){
possible[i] = possible_text.charAt(i);
}
for(var i=0; i<= length_var ; i++){
arr_text[i] = text.charAt(i);
}
setInterval(new_func, 100);
};
</script>
</head>
<body onload="call_func()">
<div id="text">
Hello There!
</div>
</body>
</html>
What I was planning to do can be seen on this page, as I was highly motivated to do this effect on my own.
Link : http://events.ccc.de/congress/2012/wiki/Main_Page
The header text of the page contains such animation.
Please Help
This changes the string sequentially.
function main() {
"use strict";
var counter = 0, all = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var $_inter = setInterval(function() {
var text = document.getElementById("text");
text.innerHTML = text.innerHTML.substring(0, counter) + all.charAt(Math.floor(Math.random()*all.length)) + text.innerHTML.substring(counter+1);
counter = (counter+1)%text.innerHTML.length;
}, 100);
}
window.onload = main;
Try this-
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript">
function randString(len) {
"use strict";
var i, out="", all ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (i = 0; i < len; i++) {
out += all.charAt(Math.floor(Math.random()*all.length));
}
return out;
}
function main() {
"use strict";
var $_inter = setInterval(function() {
var text = document.getElementById("text");
text.innerHTML = randString(text.innerHTML.length);
}, 100);
}
window.onload = main;
</script>
</head>
<body>
<div id="text">Hello World!</div>
</body>
</html>
Here take a look at this JSFiddle, as put together from this resource. It is very fast and you can easily configure it.
Basically creates a blank canvas and renders the graphics. Here is the JS and HTML code that does that:
HTML:
<html style="background:black; width:100%; height:100%">
<body style="background:black; width:100%; height:100%">
<canvas id="c" style="display: block;"></canvas>
</body>
</html>
JS:
var c = document.getElementById("c");
var ctx = c.getContext("2d");
//making the canvas full screen
c.height = window.innerHeight;
c.width = window.innerWidth;
//chinese characters - taken from the unicode charset
var chinese = "田由甲申甴电甶男甸甹町画甼甽甾甿畀畁畂畃畄畅畆畇畈畉畊畋界畍畎畏畐畑";
//converting the string into an array of single characters
chinese = chinese.split("");
var font_size = 10;
var columns = c.width/font_size; //number of columns for the rain
//an array of drops - one per column
var drops = [];
//x below is the x coordinate
//1 = y co-ordinate of the drop(same for every drop initially)
for(var x = 0; x < columns; x++)
drops[x] = 1;
//drawing the characters
function draw()
{
//Black BG for the canvas
//translucent BG to show trail
ctx.fillStyle = "rgba(0, 0, 0, 0.05)";
ctx.fillRect(0, 0, c.width, c.height);
ctx.fillStyle = "#0F0"; //green text
ctx.font = font_size + "px arial";
//looping over drops
for(var i = 0; i < drops.length; i++)
{
//a random chinese character to print
var text = chinese[Math.floor(Math.random()*chinese.length)];
//x = i*font_size, y = value of drops[i]*font_size
ctx.fillText(text, i*font_size, drops[i]*font_size);
//sending the drop back to the top randomly after it has crossed the screen
//adding a randomness to the reset to make the drops scattered on the Y axis
if(drops[i]*font_size > c.height && Math.random() > 0.975)
drops[i] = 0;
//incrementing Y coordinate
drops[i]++;
}
}
setInterval(draw, 33);
The crux of the program that's on the site (which is really, really ugly) involves creating an array of "haXX0r-like" characters, and then injecting them and removing them from the text.
They also speed up and slow down their process, and bounce between doing an addition pass and a removal pass, from what I saw on my quick read-through.
The downside of their code are that it's all a bunch of loops slopped together, with a bunch of "if"s to contain two or three loops, one after the other... ...and then they add "mode-switching" to that, where they say "if we're in mode-1 add stuff and do it quickly, and if we're in mode-2, remove stuff and lower the speed and if we're in this submode of either mode, set the speed accordingly, and if the speed is greater than this or less than that, and we're in this mode, then stop what we're doing, wait 5 seconds and call it again"...
...not pretty.
But they're starting with a quote, finding a random spot in the string, and then replacing the character at that spot with the character, plus the new "<", "?", "{", etc...
And speeding up and slowing down, as they add and remove the randomly-chosen character-type.
Elegant snippet
canvas = document.body.appendChild(document.createElement('canvas'))
screen = window.screen;
width = canvas.width = screen.width;
height = canvas.height = screen.height;
p = Array(255).join(1).split('');
context = canvas.getContext('2d');
setInterval(function(){
context.fillStyle = 'rgba(0,0,0,0.05)';
context.fillRect(0, 0, width, height);
context.fillStyle = 'rgba(0,255,0,1)';
p = p.map(function(v,i){
r = Math.random();
context.fillText(String.fromCharCode(Math.floor(2720 + r * 33)),i*10,v);
v += 10;
return v > 768 + r * 1e4 ? 0 : v
})
}, 33)
If you want something that will randomise a string and slowly replace each character with the original, here's something that may suit. The replacement order is random too, so the string is replaced out of order but ends up with the original string.
A fancier solution is to give each letter its own timer with a different lag so they run at different speeds, and for different lengths of time. But that might suck some serious system resources for a big string.
function Randomiser(el, count, delay) {
this.element = el;
this.originalText = el.textContent || el.innerText || '';
this.places = [];
this.currentText = [];
this.count = count || 3; // iterations before fixing a character
this.delay = delay || 100; // milliseconds between updates
this.iteration = 0;
this.startTime = new Date();
var i = this.originalText.length;
while (i--) {
this.places[i] = [i];
}
}
Randomiser.prototype.randomise = function() {
var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
var i = this.places.length;
while (i--) {
this.currentText[this.places[i]] = charSet.charAt((Math.random() * charSet.length) | 0);
}
this.iteration += 1;
}
Randomiser.prototype.setContent = function() {
var t = this.currentText.join('');
if (typeof this.element.textContent == 'string') {
this.element.textContent = t;
} else {
this.element.innerText = t;
}
}
Randomiser.prototype.run = function() {
var n;
var temp = [];
// If first run, randomise to initiate
if (!this.iteration) {
this.randomise();
}
// If there are places left
if (this.places.length) {
// If reached count, randomly remove one place and set its character
// to the original value
if (!(this.iteration % this.count)) {
n = this.places.splice((Math.random() * this.places.length|0), 1)[0];
this.currentText[n] = this.originalText.charAt(n);
}
// Randomise the string and call itself
this.randomise();
this.setContent();
var randomiser = this;
setTimeout(function(){randomiser.run();}, this.delay);
}
// If no places left, end
}
// Kick it off
var r = new Randomiser(document.getElementById('text'), 5, 200);
r.run();
The above uses classic prototype inheritance, it could use a closure for the same thing. Also all those prototype functions could be put in a single object that is assigned to Randomiser.prototype.

Categories

Resources