Reputation: 456
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>
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;
Upvotes: 0
Views: 2230
Reputation: 456
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>
Upvotes: 0
Reputation: 54026
ctx.measureText
You need to use ctx.measureText
and get the the width of each character, then you can space them correctly.
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
;
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
...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>
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>
Upvotes: 1