Create Texture on canvas - javascript

I want to create a texture on a canvas. You can choose between 2 and 5 colours. For each colour you can specify the percentage. The distribution of the colours on the canvas should be even.
I hold the possible colors in an array and randomly select the index and thus assign the color. If i choose a big mixing ratio (i.e. 60% and 40%) it seems to work. But if the ratio gets smaller(i.e. 90%/10%), the result is not ok.
The problem is that towards the end of the line only the color with 90% appears.
For pixelmanipulation i use ImageData. My idea was to manipulate "row by row".
Please bear with me, I am JS beginner and do not speak English native
This is my Code:
generateTexture(colors) {
if (colors.length > 0) {
let myColors = colors.slice();
let canvas = document.getElementById('mycanvas');
const w = canvas.getBoundingClientRect().width * 4;
const h = canvas.getBoundingClientRect().height * 4;
let context = canvas.getContext('2d');
let i = 0;
let sum = w * h;
for (i = 0; i < myColors.length; i++) {
myColors[i].sum = (sum / 100) * myColors[i].percentage;
myColors[i].rowsum = myColors[i].sum / h;
}
let imageData = context.getImageData(0, 0, w, h);
let data = imageData.data;
let height = 0;
while (height <= h) {
for (i = 0; i < myColors.length; i++) {
myColors[i].sum = myColors[i].rowsum;
}
let start = 0;
start = height * h;
let end = start + w * 4;
let x = start;
while (x < end) {
let colIndex = 0;
if (colors.length > 1) {
colIndex = Math.floor(Math.random() * myColors.length);
}
if (myColors[colIndex].sum > 0) {
let val = myColors[colIndex].color.split(',');
let r = parseInt(val[0]);
let g = parseInt(val[1]);
let b = parseInt(val[2]);
data[x] = r;
data[x + 1] = g;
data[x + 2] = b;
data[x + 3] = 255;
myColors[colIndex].sum = myColors[colIndex].sum - 1;
x = x + 4;
}
}
height++;
}
context.putImageData(imageData, 0, 0);
canvas.style.webkitFilter = 'blur(.35px)';
}
},
},

There are several problems with your function.
Canvas resolution
canvas.getBoundingClientRect().width may or may not return the canvas resolution. What it returns is the size on the page. Use canvas.width and canvas.height to get the resolution of the canvas.
Wrong height value
You multiply height by 4. Thus the total number of pixels channels (RGBA) you are filling is w * 4 * h * 4 which 4 times too many. It should be w * h * 4
Randomness
The function generateTexture is not truly random as you are randomly picking from a small number of colors which does not represent the true distribution you are after with the percentages.
Say you have 90% blue and 10% red.
When you pick a color you randomly pick blue or red, 50/50 even odds, so for the first 20% of the image it will be 50% blue and 50% red. Then you run out of red (used up the 10%) and you have nothing to pick from but blue.
The result is bands of random distribution till the last which is just one color.
Example of poor distribution
Example of how your selection results in a very non random texture. (Banding)
function generateTexture(colors, ctx) {
var i;
const colSel = [...colors];
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const data32 = new Uint32Array(imgData.data.buffer);
const pixels = data32.length;
for (const col of colSel) {
const rgb = col.color.split(',');
col.RGBA32 = 0xFF000000 + ((rgb[2] & 0xFF) << 16) + ((rgb[1] & 0xFF) << 8) + (rgb[0] & 0xFF);
col.pixels = Math.round(pixels * (col.percentage / 100));
}
i = 0;
while (i < pixels) {
const idx = Math.random() * colSel.length | 0;
const col = colSel[idx];
data32[i++] = col.RGBA32;
col.pixels --;
if (col.pixels <= 0) {
colSel.splice(idx, 1);
if (colSel.length === 1) {
const col = colSel[0];
while (i < pixels) { data32[i++] = col.RGBA32 }
}
}
}
ctx.putImageData(imgData, 0, 0);
}
const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
colors.length = colCount = 0;
colList.innerHTML = "";
while(colCount < 100) {
const col = randomColor();
colors.push(col);
$$(colList, $$($("li"),
$("span", {textContent: col.percentage + "%"}),
$("span", { className: "colBox", style: "background:rgb("+col.color+ ");"})
));
}
generateTexture(colors, canvas.getContext("2d"));
}
const randByte = () => Math.random() * 255 | 0;
function randomColor() {
const remaining = sum - colCount;
const percentage = remaining < 5 ? remaining : (Math.random()** 0.33)* remaining | 0;
colCount += percentage;
return {
color: [randByte(), randByte(), randByte()].join(","),
percentage,
}
}
const $$ = (el, ...sibs) => sibs.reduce((e,sib)=>(e.appendChild(sib), e), el);
const $ = (tag, p ={}) => Object.assign(document.createElement(tag),p);
randomize();
* {margin: 0px; font-family: arial}
canvas {
position: absolute;
top: opx;
left: 0px;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
div {
position: absolute;
top: 20px;
left: 20px;
background: white;
padding: 2px 4px;
border: 1px solid black;
}
#colList {
}
.colBox {
width: 64px;
height: 1.2em;
position: absolute;
right: 4px;
}
<canvas id="canvas" width="75" height="30"></canvas>
<div>Click for another example run
<ul id="colList"> </ul>
</div>
Random items by percentage
You say this is a texture so, does it really matter if the number of actual pixel match the needed percentage.
If you create an array with 100 colors in it. For the 90% color add it to the array 90 times, for the 10% add it 10 times.
Then randomly pick from that array and you will get the distribution you want.
Example true random distribution
function generateTexture(colors, ctx) {
var len, i, colSel = [];
for (const col of colors) {
const rgb = col.color.split(',');
const RGBA32 = 0xFF000000 + ((rgb[2] & 0xFF) << 16) + ((rgb[1] & 0xFF) << 8) + (rgb[0] & 0xFF);
i = col.percentage;
while (i-- > 0) { colSel.push(RGBA32) }
}
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const data32 = new Uint32Array(imgData.data.buffer);
i = len = data32.length;
while (i-- > 0) { data32[i] = colSel[Math.random() * 100 | 0] }
ctx.putImageData(imgData, 0, 0);
}
const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
colors.length = colCount = 0;
colList.innerHTML = "";
while(colCount < 100) {
const col = randomColor();
colors.push(col);
$$(colList, $$($("li"),
$("span", {textContent: col.percentage + "%"}),
$("span", { className: "colBox", style: "background:rgb("+col.color+ ");"})
));
}
generateTexture(colors, canvas.getContext("2d"));
}
const randByte = () => Math.random() * 255 | 0;
function randomColor() {
const remaining = sum - colCount;
const percentage = remaining < 5 ? remaining : (Math.random()** 0.33)* remaining | 0;
colCount += percentage;
return {
color: [randByte(), randByte(), randByte()].join(","),
percentage,
}
}
const $$ = (el, ...sibs) => sibs.reduce((e,sib)=>(e.appendChild(sib), e), el);
const $ = (tag, p ={}) => Object.assign(document.createElement(tag),p);
randomize();
* {margin: 0px; font-family: arial}
canvas {
position: absolute;
top: opx;
left: 0px;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
div {
position: absolute;
top: 20px;
left: 20px;
background: white;
padding: 2px 4px;
border: 1px solid black;
}
#colList {
}
.colBox {
width: 64px;
height: 1.2em;
position: absolute;
right: 4px;
}
<canvas id="canvas" width="75" height="30"></canvas>
<div>Click for random texture
<ul id="colList"> </ul>
</div>
Shuffle pixels
If it is very important that the correct number of pixels for each color is in the image then you can use a random shuffle.
Add the colors 1 by 1 to the image to the exact counts needed.
Then randomly shuffle the pixels.
Example Random shuffle
function generateTexture(colors, ctx) {
var i, idx = 0, RGBA32;
const imgData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
const data32 = new Uint32Array(imgData.data.buffer);
const pixels = data32.length;
for (const col of colors) {
const rgb = col.color.split(',');
RGBA32 = 0xFF000000 + ((rgb[2] & 0xFF) << 16) + ((rgb[1] & 0xFF) << 8) + (rgb[0] & 0xFF);
i = Math.round((col.percentage / 100) * pixels);
while(i-- >= 0) { data32[idx++] = RGBA32 }
}
// fill any remaining pixels with last color
while(idx < pixels) { data32[idx++] = RGBA32 };
// shuffle pixels
i = 0;
while (i < pixels) {
const rIdx = Math.random() * (pixels-i) + i | 0;
const p = data32[i];
data32[i++] = data32[rIdx]
data32[rIdx] = p;
}
ctx.putImageData(imgData, 0, 0);
}
const colors = [];
const sum = 100;
var colCount = 0;
canvas.addEventListener("click", randomize);
function randomize(){
colors.length = colCount = 0;
colList.innerHTML = "";
while(colCount < 100) {
const col = randomColor();
colors.push(col);
$$(colList, $$($("li"),
$("span", {textContent: col.percentage + "%"}),
$("span", { className: "colBox", style: "background:rgb("+col.color+ ");"})
));
}
generateTexture(colors, canvas.getContext("2d"));
}
const randByte = () => Math.random() * 255 | 0;
function randomColor() {
const remaining = sum - colCount;
const percentage = remaining < 5 ? remaining : (Math.random()** 0.33)* remaining | 0;
colCount += percentage;
return {
color: [randByte(), randByte(), randByte()].join(","),
percentage,
}
}
const $$ = (el, ...sibs) => sibs.reduce((e,sib)=>(e.appendChild(sib), e), el);
const $ = (tag, p ={}) => Object.assign(document.createElement(tag),p);
randomize();
* {margin: 0px; font-family: arial}
canvas {
position: absolute;
top: opx;
left: 0px;
width: 100%;
height: 100%;
image-rendering: pixelated;
}
div {
position: absolute;
top: 20px;
left: 20px;
background: white;
padding: 2px 4px;
border: 1px solid black;
}
#colList {
}
.colBox {
width: 64px;
height: 1.2em;
position: absolute;
right: 4px;
}
<canvas id="canvas" width="75" height="30"></canvas>
<div>Click for random texture
<ul id="colList"> </ul>
</div>

Related

Resizing font on a canvas

I use the wheel of fortune by Roko C. Buljan from here: how to draw a wheel of fortune?
This is my first experience with JS and canvas.
I want to put long sentences in labels, but they go out of bounds.
I've tried using a flexible font, but that doesn't really work and isn't exactly what I need. I need the text inside each block to move to a new line and reduce the font if it goes out of bounds.
Can this be implemented in this code, or would I need to write everything from scratch?
const sectors = [
{color:"#f82", label:"parallellogram into parallellogram"},
{color:"#0bf", label:"10"},
{color:"#fb0", label:"StackStack StackStack"},
{color:"#0fb", label:"50"},
{color:"#b0f", label:"StackStackStackStackStackStack"},
{color:"#f0b", label:"Stack Stack"},
{color:"#bf0", label:"Stack"},
];
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const EL_spin = document.querySelector("#spin");
const ctx = document.querySelector("#wheel").getContext('2d');
const dia = ctx.canvas.width;
const rad = dia / 2;
const PI = Math.PI;
const TAU = 2 * PI;
const arc = TAU / sectors.length;
const friction = 0.991; // 0.995=soft, 0.99=mid, 0.98=hard
let angVel = 0; // Angular velocity
let ang = 0; // Angle in radians
const getIndex = () => Math.floor(tot - ang / TAU * tot) % tot;
function drawSector(sector, i) {
const ang = arc * i;
ctx.save();
// COLOR
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(rad, rad);
ctx.arc(rad, rad, rad, ang, ang + arc);
ctx.lineTo(rad, rad);
ctx.fill();
// TEXT
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "right";
ctx.fillStyle = "#fff";
ctx.font = "bold 14px sans-serif";
ctx.fillText(sector.label, rad - 10, 10);
//
ctx.restore();
};
function rotate() {
const sector = sectors[getIndex()];
ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
EL_spin.textContent = !angVel ? "SPIN" : sector.label;
EL_spin.style.background = sector.color;
}
function finishedSpinning() { // Called when the wheel stops spinning
const sector = sectors[getIndex()];
alert(sector.label);
}
function frame() {
if (!angVel) return;
const isSpinning = angVel > 0; // Check if the wheel is currently spinning
angVel *= friction; // Decrement velocity by friction
if (angVel < 0.002) angVel = 0; // Bring to stop
ang += angVel; // Update angle
ang %= TAU; // Normalize angle
rotate();
if (isSpinning && angVel === 0) { // If the wheel was spinning, but isn't anymore, it has just stopped
finishedSpinning();
}
}
function engine() {
frame();
requestAnimationFrame(engine)
}
// INIT
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
EL_spin.addEventListener("click", () => {
if (!angVel) angVel = rand(0.25, 0.35);
});
#wheelOfFortune {
display: inline-block;
position: relative;
overflow: hidden;
}
#wheel {
display: block;
}
#spin {
font: 1.5em/0 sans-serif;
user-select: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 50%;
left: 50%;
width: 30%;
height: 30%;
margin: -15%;
background: #fff;
color: #fff;
box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6);
border-radius: 50%;
transition: 0.8s;
}
#spin::after {
content: "";
position: absolute;
top: -17px;
border: 10px solid transparent;
border-bottom-color: currentColor;
border-top: none;
}
<div id="wheelOfFortune">
<canvas id="wheel" width="300" height="300"></canvas>
<div id="spin">SPIN</div>
</div>
That's what I'm trying to do
Solution: HTML5 canvas ctx.fillText won't do line breaks?
New problem - long words as in the first label
Here's what I got, ready for any suggestions for improvement
(mainly the part with text centering - line 89)
It would also be nice to add a shadow to the text to stand out against the background of bright colors
New problem - long words as in the first label
const sectors = [
{ color: "#0fb", label: "Параллелограмм в паралеллограмме" },
{ color: "#0bf", label: "Бесплатная настройка виджета" },
{ color: "#fb0", label: "Два пресета цветов по цене одного" },
{ color: "#0fb", label: "1строчка" },
{ color: "#b0f", label: "Год премиум поддержки в подарок в подарок" },
{ color: "#f0b", label: "Скидка 10% на любой пакет" },
{ color: "#bf0", label: "Виджет 'Juice Contact' в подарок" },
{ color: "#f82", label: "Скидка 5% на любой пакет" },
{ color: "#bf0", label: "" },
];
function printAtWordWrap(context, text, x, y, lineHeight, fitWidth) {
fitWidth = fitWidth || 0;
if (fitWidth <= 0) {
context.fillText(text, x, y);
return;
}
let words = text.split(" ");
let currentLine = 0;
let idx = 1;
while (words.length > 0 && idx <= words.length) {
const str = words.slice(0, idx).join(" ");
const w = context.measureText(str).width;
if (w > fitWidth) {
if (idx == 1) {
idx = 2;
}
context.fillText(
words.slice(0, idx - 1).join(" "),
x,
y + lineHeight * currentLine
);
currentLine++;
words = words.splice(idx - 1);
idx = 1;
} else {
idx++;
}
}
if (idx > 0)
context.fillText(words.join(" "), x, y + lineHeight * currentLine);
}
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const EL_spin = document.querySelector("#spin");
const ctx = document.querySelector("#wheel").getContext("2d");
const dia = ctx.canvas.width;
const rad = dia / 2;
const PI = Math.PI;
const TAU = 2 * PI;
const arc = TAU / sectors.length;
const friction = 0.991; // 0.995=soft, 0.99=mid, 0.98=hard
let angVel = 0; // Angular velocity
let ang = 0; // Angle in radians
const getIndex = () => Math.floor(tot - (ang / TAU) * tot) % tot;
// calcFontSize not used
const calcFontSize = () => {
const maxChars = Math.max(...sectors.map((ob) => ob.label.length));
const width = rad * 0.9 - 15;
const w = (width / maxChars) * 1.3;
const h = ((TAU * rad) / tot) * 0.5;
return Math.min(w, h);
};
function drawSector(sector, i) {
const ang = arc * i;
ctx.save();
// COLOR
ctx.beginPath();
ctx.fillStyle = sector.color;
ctx.moveTo(rad, rad);
ctx.arc(rad, rad, rad, ang, ang + arc);
ctx.lineTo(rad, rad);
ctx.fill();
// TEXT
ctx.translate(rad, rad);
ctx.rotate(ang + arc / 2);
ctx.textAlign = "center";
ctx.fillStyle = "#fff";
const fontSize = 15;
ctx.font = `bold ${fontSize}px sans-serif`;
// values for centering text - not ideal for now (need tuning)
const w = ctx.measureText(sector.label).width;
console.log(sector.label, w);
let y;
const lineWidth = 130; // width before line break
switch (true) {
case w < lineWidth:
y = fontSize / 3;
break;
case w >= lineWidth && w < 2 * lineWidth:
y = fontSize / 3 - 10;
break;
case w >= 2 * lineWidth && w < 3 * lineWidth:
y = fontSize / 3 - 20;
break;
case w >= 3 * lineWidth && w < 4 * lineWidth:
y = fontSize / 3 - 30;
break;
case w >= 4 * lineWidth:
y = fontSize / 3 - 40;
break;
default:
y = fontSize / 3;
}
printAtWordWrap(ctx, sector.label, rad * 0.7, y, 21, lineWidth);
ctx.restore();
}
function rotate() {
const sector = sectors[getIndex()];
ctx.canvas.style.transform = `rotate(${ang - PI / 2}rad)`;
EL_spin.textContent = !angVel
? "SPIN"
: ""; /* sector.label instead "" - if u want to display text inside spin element */
EL_spin.style.background = sector.color;
}
function finishedSpinning() {
// Called when the wheel stops spinning
const sector = sectors[getIndex()];
alert(sector.label);
}
function frame() {
if (!angVel) return;
const isSpinning = angVel > 0; // Check if the wheel is currently spinning
angVel *= friction; // Decrement velocity by friction
if (angVel < 0.002) angVel = 0; // Bring to stop
ang += angVel; // Update angle
ang %= TAU; // Normalize angle
rotate();
if (isSpinning && angVel === 0) {
// If the wheel was spinning, but isn't anymore, it has just stopped
finishedSpinning();
}
}
function engine() {
frame();
requestAnimationFrame(engine);
}
// INIT
sectors.forEach(drawSector);
rotate(); // Initial rotation
engine(); // Start engine
EL_spin.addEventListener("click", () => {
if (!angVel) angVel = rand(0.25, 0.35);
});
#wheelOfFortune {
display: inline-block;
position: relative;
overflow: hidden;
}
#wheel {
display: block;
}
#spin {
font: 1.5em/0 sans-serif;
user-select: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 50%;
left: 50%;
width: 30%;
height: 30%;
margin: -15%;
background: #fff;
color: #fff;
box-shadow: 0 0 0 8px currentColor, 0 0px 15px 5px rgba(0, 0, 0, 0.6);
border-radius: 50%;
transition: 0.8s;
}
#spin::after {
content: "";
position: absolute;
top: -17px;
border: 10px solid transparent;
border-bottom-color: currentColor;
border-top: none;
}
<body style="background-color: darkcyan">
<div id="wheelOfFortune">
<canvas id="wheel" width="480" height="480"></canvas>
<div id="spin">SPIN</div>
</div>
</body>

Not able to get the input text box editable inside animated javascript

I have added the code snippet over here. I was trying to do some random exercise. Can someone look why my textbox is not editable. There is falling leaf animation over here. Along with I have added one textbox on top of it. But currently I am not able to add any text to the textbox.
I am just adding more text in order to overcome the error message that the post is mostly having code and not much explanation.
var LeafScene = function(el) {
this.viewport = el;
this.world = document.createElement('div');
this.leaves = [];
this.options = {
numLeaves: 20,
wind: {
magnitude: 1.2,
maxSpeed: 12,
duration: 300,
start: 0,
speed: 0
},
};
this.width = this.viewport.offsetWidth;
this.height = this.viewport.offsetHeight;
// animation helper
this.timer = 0;
this._resetLeaf = function(leaf) {
// place leaf towards the top left
leaf.x = this.width * 2 - Math.random()*this.width*1.75;
leaf.y = -10;
leaf.z = Math.random()*200;
if (leaf.x > this.width) {
leaf.x = this.width + 10;
leaf.y = Math.random()*this.height/2;
}
// at the start, the leaf can be anywhere
if (this.timer == 0) {
leaf.y = Math.random()*this.height;
}
// Choose axis of rotation.
// If axis is not X, chose a random static x-rotation for greater variability
leaf.rotation.speed = Math.random()*10;
var randomAxis = Math.random();
if (randomAxis > 0.5) {
leaf.rotation.axis = 'X';
} else if (randomAxis > 0.25) {
leaf.rotation.axis = 'Y';
leaf.rotation.x = Math.random()*180 + 90;
} else {
leaf.rotation.axis = 'Z';
leaf.rotation.x = Math.random()*360 - 180;
// looks weird if the rotation is too fast around this axis
leaf.rotation.speed = Math.random()*3;
}
// random speed
leaf.xSpeedVariation = Math.random() * 0.8 - 0.4;
leaf.ySpeed = Math.random() + 1.5;
return leaf;
}
this._updateLeaf = function(leaf) {
var leafWindSpeed = this.options.wind.speed(this.timer - this.options.wind.start, leaf.y);
var xSpeed = leafWindSpeed + leaf.xSpeedVariation;
leaf.x -= xSpeed;
leaf.y += leaf.ySpeed;
leaf.rotation.value += leaf.rotation.speed;
var t = 'translateX( ' + leaf.x + 'px ) translateY( ' + leaf.y + 'px ) translateZ( ' + leaf.z + 'px ) rotate' + leaf.rotation.axis + '( ' + leaf.rotation.value + 'deg )';
if (leaf.rotation.axis !== 'X') {
t += ' rotateX(' + leaf.rotation.x + 'deg)';
}
leaf.el.style.webkitTransform = t;
leaf.el.style.MozTransform = t;
leaf.el.style.oTransform = t;
leaf.el.style.transform = t;
// reset if out of view
if (leaf.x < -10 || leaf.y > this.height + 10) {
this._resetLeaf(leaf);
}
}
this._updateWind = function() {
// wind follows a sine curve: asin(b*time + c) + a
// where a = wind magnitude as a function of leaf position, b = wind.duration, c = offset
// wind duration should be related to wind magnitude, e.g. higher windspeed means longer gust duration
if (this.timer === 0 || this.timer > (this.options.wind.start + this.options.wind.duration)) {
this.options.wind.magnitude = Math.random() * this.options.wind.maxSpeed;
this.options.wind.duration = this.options.wind.magnitude * 50 + (Math.random() * 20 - 10);
this.options.wind.start = this.timer;
var screenHeight = this.height;
this.options.wind.speed = function(t, y) {
// should go from full wind speed at the top, to 1/2 speed at the bottom, using leaf Y
var a = this.magnitude/2 * (screenHeight - 2*y/3)/screenHeight;
return a * Math.sin(2*Math.PI/this.duration * t + (3 * Math.PI/2)) + a;
}
}
}
}
LeafScene.prototype.init = function() {
for (var i = 0; i < this.options.numLeaves; i++) {
var leaf = {
el: document.createElement('div'),
x: 0,
y: 0,
z: 0,
rotation: {
axis: 'X',
value: 0,
speed: 0,
x: 0
},
xSpeedVariation: 0,
ySpeed: 0,
path: {
type: 1,
start: 0,
},
image: 1
};
this._resetLeaf(leaf);
this.leaves.push(leaf);
this.world.appendChild(leaf.el);
}
this.world.className = 'leaf-scene';
this.viewport.appendChild(this.world);
// set perspective
this.world.style.webkitPerspective = "400px";
this.world.style.MozPerspective = "400px";
this.world.style.oPerspective = "400px";
this.world.style.perspective = "400px";
// reset window height/width on resize
var self = this;
window.onresize = function(event) {
self.width = self.viewport.offsetWidth;
self.height = self.viewport.offsetHeight;
};
}
LeafScene.prototype.render = function() {
this._updateWind();
for (var i = 0; i < this.leaves.length; i++) {
this._updateLeaf(this.leaves[i]);
}
this.timer++;
requestAnimationFrame(this.render.bind(this));
}
// start up leaf scene
var leafContainer = document.querySelector('.falling-leaves'),
leaves = new LeafScene(leafContainer);
leaves.init();
leaves.render();
body, html {
height: 100%;
}
form {
width: 600px;
margin: 0px auto;
padding: 15px;
}
input[type=text] {
display: block;
padding: 10px;
box-sizing: border-box;
font-size: x-large;
margin-top: 25%;
}
input[type=text] {
width: 100%;
margin-bottom: 15px;
}
.falling-leaves {
position: absolute;
top: 0;
bottom: 0;
left: 50%;
width: 100%;
max-width: 880px;
max-height: 880px; /* image is only 880x880 */
transform: translate(-50%, 0);
border: 20px solid #fff;
border-radius: 50px;
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/125707/sidebar-bg.png) no-repeat center center;
background-size: cover;
overflow: hidden;
}
.leaf-scene {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 100%;
transform-style: preserve-3d;
}
.leaf-scene div {
position: absolute;
top: 0;
left: 0;
width: 20px;
height: 20px;
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/125707/leaf.svg) no-repeat;
background-size: 100%;
transform-style: preserve-3d;
backface-visibility: visible;
}
<html>
<head>
<link rel="stylesheet" type="text/css" href="style/style.css">
</head>
<body>
<div class="falling-leaves">
<form id="frmContact">
<input type="text" id="txtName" name="txtName" placeholder="Text goes here">
</form>
</div>
<script src="script/script.js"></script>
</body>
</html>

Different scroll speeds for elements in array

I have different randomly-styled stars in an array and I would like them to each have different scroll speeds between -.2 and -.8. The idea is to have them do a parallax effect, and it'd be cool to have everything a little random.
This was the original scroll speed code using images:
var starsOne = document.querySelector("#starsOne");
function setTranslate(xPos, yPos, el) {
el.style.transform = "translate3d(" + xPos + ", " + yPos + "px, 0)";
}
var xScrollPosition;
var yScrollPosition;
function scrollLoop() {
xScrollPosition = window.scrollX;
yScrollPosition = window.scrollY;
setTranslate(0, yScrollPosition * -0.6, starsOne);
requestAnimationFrame(scrollLoop);
}
window.addEventListener("load", scrollLoop, false);
I've been trying to integrate the above code somehow for the array:
let starScrollMin = 2;
let starScrollMax = 8;
var starScrollSpeed = -Math.abs((Math.floor(Math.random() * (starScrollMax - starScrollMin + 1)) + starScrollMin) / 10);
function starScroll() {
for (i = 0; i < starDivvyArr.length; i++) {
yScrollPos = window.scrollY;
starDivvyArr[i].style.transform = "translate3d(" + 0 + "px, " + yScrollPos * starScrollSpeed + "px, 0)";
}
requestAnimationFrame(starScroll);
}
window.addEventListener("load", starScroll, false);
If starScrollSpeed is global, then all the stars move in a big chunk. If it's within the starScroll() function, the values are at least different per star, but it gets crazy as it keeps randomizing and multiplying.
Any ideas on how to randomize the scroll speed for each star so it looks like a parallax effect, without them moving in one single chunk or going crazy? Or is it a safer bet to make a bunch of css lines and then randomly assign classes?
jsfiddle
It's a bit unclear what exactly you're after.
If you're trying to set a random scroll speed for each star, and not change it every time the startScroll triggers.
you could set a speed for each start separately inside the loop and use that in the startScroll function instead:
...
starDivvyArr[i].starScrollSpeed = -Math.abs((Math.floor(Math.random() * (starScrollMax - starScrollMin + 1)) + starScrollMin) / 10);
...
starDivvyArr[i].style.transform = "translate3d(" + 0 + "px, " + yScrollPos * starDivvyArr[i].starScrollSpeed + "px, 0)";
var starCount = 25;
let starContain = document.getElementById('starContain');
/*Create star divs*/
for (var i = 0; i < starCount; i++) {
var starDiv = document.createElement("div");
starDiv.className = 'star';
starContain.append(starDiv);
}
/*Make an array from the star divs*/
var starDivvy = document.querySelectorAll(".star");
var starDivvyArr = Array.from(starDivvy);
/*Create some possible styles*/
var starColor = ['yellow', 'blue', 'white'];
var starSizeMin = 5;
var starSizeMax = 25;
let starContainWidth = starContain.offsetWidth;
let starContainHeight = starContain.offsetHeight;
let starScrollMin = 2;
let starScrollMax = 8;
/*Give the star array some different styles*/
for (i = 0; i < starDivvyArr.length; i++) {
/*Change star color*/
starDivvyArr[i].style.backgroundColor = starColor[Math.floor(Math.random() * starColor.length)];
/*Change star position within container*/
starDivvyArr[i].style.left = Math.floor((Math.random() * starContainWidth) + 1) + "px";
starDivvyArr[i].style.top = Math.floor((Math.random() * starContainHeight) + 1) + "px";
/*Change star size*/
starDivvyArr[i].style.width = Math.floor(Math.random() * (starSizeMax - starSizeMin + 1)) + starSizeMin + "px";
starDivvyArr[i].style.height = starDivvyArr[i].style.width;
starDivvyArr[i].starScrollSpeed = -Math.abs((Math.floor(Math.random() * (starScrollMax - starScrollMin + 1)) + starScrollMin) / 10);
}
/*>>>>>>>POINT OF CONFUSION<<<<<<<*/
/*Randomize scroll speed between -0.2 and -0.8*/
/*Give the stars a scrolling function*/
function starScroll() {
for (i = 0; i < starDivvyArr.length; i++) {
yScrollPos = window.scrollY;
starDivvyArr[i].style.transform = "translate3d(" + 0 + "px, " + yScrollPos * starDivvyArr[i].starScrollSpeed + "px, 0)";
}
requestAnimationFrame(starScroll);
}
window.addEventListener("load", starScroll, false);
*{
margin:0 auto;
}
section{
width:100vw;
height:150vh;
}
.section1{
background-color:#000;
}
.section2{
background-color:#202;
}
h2{
text-align:center;
color:#ccc;
}
#starContain{
width:100vw;
height:500px;
position:absolute;
top:50vh;
z-index:2;
background-color:rgba(255,255,255,0.2);
}
.star{
background-color:green;
position:absolute;
z-index:100;
border-radius:50%;
}
<main>
<section class="section1">
<h2>
Section 1
</h2>
</section>
<div id="starContain">
</div>
<section class="section2">
<h2>
Section 2
</h2>
</section>
</main>
You could create an array for the randomly created numbers and push a value from a function that returns the randomly created speed into that array with in the loop and then use that array to get a random value for each element.
var starCount = 25;
let starContain = document.getElementById('starContain');
/*Create star divs*/
for (var i = 0; i < starCount; i++) {
var starDiv = document.createElement("div");
starDiv.className = 'star';
starContain.append(starDiv);
}
/*Make an array from the star divs*/
var starDivvy = document.querySelectorAll(".star");
var starDivvyArr = Array.from(starDivvy);
/*Create some possible styles*/
var starColor = ['yellow', 'blue', 'white'];
var starSizeMin = 5;
var starSizeMax = 25;
let starContainWidth = starContain.offsetWidth;
let starContainHeight = starContain.offsetHeight;
/*Give the star array some different styles*/
for (i = 0; i < starDivvyArr.length; i++) {
/*Change star color*/
starDivvyArr[i].style.backgroundColor = starColor[Math.floor(Math.random() * starColor.length)];
/*Change star position within container*/
starDivvyArr[i].style.left = Math.floor((Math.random() * starContainWidth) + 1) + "px";
starDivvyArr[i].style.top = Math.floor((Math.random() * starContainHeight) + 1) + "px";
/*Change star size*/
starDivvyArr[i].style.width = Math.floor(Math.random() * (starSizeMax - starSizeMin + 1)) + starSizeMin + "px";
starDivvyArr[i].style.height = starDivvyArr[i].style.width;
}
/*>>>>>>>POINT OF CONFUSION<<<<<<<*/
/*Randomize scroll speed between -0.2 and -0.8*/
let starScrollMin = 2;
let starScrollMax = 8;
//function for creating random scroll speed
const starScrollSpeed = (min,max) => {
return -Math.abs((Math.floor(Math.random() * (min - max + 1)) + min) / 10);
}
//==> added array
const starArray = [];
/*Give the stars a scrolling function*/
function starScroll() {
for (i = 0; i < starDivvyArr.length; i++) {
// array to hold randomly created scroll speeds
starArray.push(starScrollSpeed(starScrollMin,starScrollMax))
yScrollPos = window.scrollY;
starDivvyArr[i].style.transform = "translate3d(" + 0 + "px, " + yScrollPos * starArray[i] + "px, 0)";
}
requestAnimationFrame(starScroll);
}
window.addEventListener("load", starScroll, false);
* {
margin: 0 auto;
}
section {
width: 100vw;
height: 150vh;
}
.section1 {
background-color: #000;
}
.section2 {
background-color: #202;
}
h2 {
text-align: center;
color: #ccc;
}
#starContain {
width: 100vw;
height: 500px;
position: absolute;
top: 50vh;
z-index: 2;
background-color: rgba(255, 255, 255, 0.2);
}
.star {
background-color: green;
position: absolute;
z-index: 100;
border-radius: 50%;
}
<main>
<section class="section1">
<h2>
Section 1
</h2>
</section>
<div id="starContain">
</div>
<section class="section2">
<h2>
Section 2
</h2>
</section>
</main>

Cannot read properties of null (reading 'animate') error chrome JS animation

I am new to webdev and I am trying to use this CodePen to animate my buttons:
https://codepen.io/Mamboleoo/pen/JjdXPgR
However, I am still getting this error on chrome when trying to run the code:
Uncaught TypeError: Cannot read properties of null (reading 'animate')
I tried replacing the last JS line to: if ( document.getElementByID("aaa").animate ){} but no success.enter code here
Can anyone help me please ?
HTML
<div class="contianer">
<div class="wrapper" id="aaa">
<button data-type="square">Square particles</button>
<button data-type="emoji">Emoji particles</button>
<button data-type="mario">Mario particles</button>
<button data-type="shadow">Shadow particles</button>
<button data-type="line">Line particles</button>
</div>
<span class="preloader"></span>
</div>
CSS
particle {
position: fixed;
top: 0;
left: 0;
opacity: 0;
pointer-events: none;
background-repeat: no-repeat;
background-size: contain;
}
.wrapper {
position: absolute;
}
.wrapper button {
padding: 20px;
margin: 10px;
align-self: center;
}
.preloader {
position: absolute;
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/127738/mario-face.png);
}
JS:
function pop (e) {
let amount = 30;
switch (e.target.dataset.type) {
case 'shadow':
case 'line':
amount = 60;
break;
}
// Quick check if user clicked the button using a keyboard
if (e.clientX === 0 && e.clientY === 0) {
const bbox = e.target.getBoundingClientRect();
const x = bbox.left + bbox.width / 2;
const y = bbox.top + bbox.height / 2;
for (let i = 0; i < 30; i++) {
// We call the function createParticle 30 times
// We pass the coordinates of the button for x & y values
createParticle(x, y, e.target.dataset.type);
}
} else {
for (let i = 0; i < amount; i++) {
createParticle(e.clientX, e.clientY + window.scrollY, e.target.dataset.type);
}
}
}
function createParticle (x, y, type) {
const particle = document.createElement('particle');
document.body.appendChild(particle);
let width = Math.floor(Math.random() * 30 + 8);
let height = width;
let destinationX = (Math.random() - 0.5) * 300;
let destinationY = (Math.random() - 0.5) * 300;
let rotation = Math.random() * 520;
let delay = Math.random() * 200;
switch (type) {
case 'square':
particle.style.background = `hsl(${Math.random() * 90 + 270}, 70%, 60%)`;
particle.style.border = '1px solid white';
break;
case 'emoji':
particle.innerHTML = ['❤','🧡','💛','💚','💙','💜','🤎'][Math.floor(Math.random() * 7)];
particle.style.fontSize = `${Math.random() * 24 + 10}px`;
width = height = 'auto';
break;
case 'mario':
particle.style.backgroundImage = 'url(https://s3-us-west-
2.amazonaws.com/s.cdpn.io/127738/mario-face.png)';
break;
case 'shadow':
var color = `hsl(${Math.random() * 90 + 90}, 70%, 50%)`;
particle.style.boxShadow = `0 0 ${Math.floor(Math.random() * 10 + 10)}px ${color}`;
particle.style.background = color;
particle.style.borderRadius = '50%';
width = height = Math.random() * 5 + 4;
break;
case 'line':
var color = `hsl(${Math.random() * 90 + 90}, 70%, 50%)`;
particle.style.background = 'black';
height = 1;
rotation += 1000;
delay = Math.random() * 1000;
break;
}
particle.style.width = `${width}px`;
particle.style.height = `${height}px`;
const animation = particle.animate([
{
transform: `translate(-50%, -50%) translate(${x}px, ${y}px) rotate(0deg)`,
opacity: 1
},
{
transform: `translate(-50%, -50%) translate(${x + destinationX}px, ${y + destinationY}px)
rotate(${rotation}deg)`,
opacity: 0
}
], {
duration: Math.random() * 1000 + 5000,
easing: 'cubic-bezier(0, .9, .57, 1)',
delay: delay
});
animation.onfinish = removeParticle;
}
function removeParticle (e) {
e.srcElement.effect.target.remove();
}
if (document.body.animate) {
document.querySelectorAll('button').forEach(button => button.addEventListener('click', pop));
}

How do I change this code so it goes from white>green>black?

I have this chunk of code that produces a clickable box that changes colors from black --> green --> white by cycling through the shades of green whenever the mouse is clicked. I need the same thing to happen, except instead of getting gradually lighter, the box starts at white and gradually becomes darker.
var div = document.querySelector('#myDiv')
div.dataset.color = 0;
div.addEventListener('click', () => {
div.dataset.color = parseInt(div.dataset.color) + 5;
var c = Math.min(div.dataset.color % 512, 255);
var c2 = Math.max((div.dataset.color % 512) - 255, 0);
div.style.background = 'rgb(' + c2 + ',' + c + ',' + c2 + ')';
})
#myDiv {
width: 200px;
height: 200px;
background: #000000;
}
<div id="myDiv"></div>
By taking your code as a base, you simply have to invert c and c2, and then substract their result from 255 to get the reverse :
var div = document.querySelector('#myDiv')
div.dataset.color = 0;
div.addEventListener('click', ()=>{
div.dataset.color = parseInt(div.dataset.color) + 5;
// invert c and c2
// c is the green channel
// c2 the red and blue ones
var c2 = Math.min(div.dataset.color % 512, 255);
var c = Math.max((div.dataset.color % 512) - 255, 0);
// substract the values from 255 to get white to black instead of b2w
div.style.background = 'rgb(' + (225 - c2) + ',' + (255 - c) + ',' + (255 - c2) + ')';
})
#myDiv {
width: 200px;
height: 200px;
background: #fff;
}
<div id="myDiv"></div>
Once the operations are cleaned it gives :
var div = document.querySelector('#myDiv')
div.dataset.color = 510;
div.addEventListener('click', ()=> {
// we substract 5 at each iteration
div.dataset.color = parseInt(div.dataset.color) - 5;
if(div.dataset.color < 0) // negative modulo
div.dataset.color = 510;
var c = Math.min(div.dataset.color, 255); // green channel
var c2 = Math.max(div.dataset.color - 255, 0); // red and blue
div.style.background = 'rgb(' + c2 + ',' + c + ',' + c2 + ')';
});
#myDiv {
width: 200px;
height: 200px;
background: #fff;
}
<div id="myDiv"></div>
And the original black to white :
var div = document.querySelector('#myDiv')
div.dataset.color = 0;
div.addEventListener('click', e => {
// we add 5 at each iteration
div.dataset.color = (parseInt(div.dataset.color) + 5) % 511;
// This doesn't move
var c = Math.min(div.dataset.color, 255); // green channel
var c2 = Math.max(div.dataset.color - 255, 0); // red and blue
div.style.background = 'rgb(' + c2 + ',' + c + ',' + c2 + ')';
});
#myDiv {
width: 200px;
height: 200px;
background: #000;
}
<div id="myDiv"></div>
And both directions :
var div = document.querySelector('#myDiv')
div.dataset.color = 0;
div.dataset.inc = 5; // add an increment value which will tell us the current direction
var f = e => { // moved to an rAF loop to avoid killing our mice
div.dataset.color = parseInt(div.dataset.color) + (+div.dataset.inc);
if(div.dataset.color < 0 || div.dataset.color > 512){
// change the direction
div.dataset.inc = div.dataset.inc * -1;
}
// This doesn't move
var c = Math.min(div.dataset.color, 255); // green channel
var c2 = Math.max(div.dataset.color - 255, 0); // red and blue
div.style.background = 'rgb(' + c2 + ',' + c + ',' + c2 + ')';
requestAnimationFrame(f);
};
f();
#myDiv {
width: 200px;
height: 200px;
background: #000;
}
<div id="myDiv"></div>
Here you are!
<div id="myDiv"></div>
<style>
#myDiv {
width: 200px;
height: 200px;
background: #000000;
}
</style>
<script>
var $div = document.querySelector('div');
var index = 1;
$div.addEventListener('click', () => {
var color;
if (index === 1) {
color = 'white';
index++
} else if (index === 2) {
color = 'green';
index++
} else if (index === 3) {
color = 'black';
index = 1;
}
$div.style.background = color
})
</script>
If you can use hsl(), the third value goes from 0 (black) to 50% (green in this case) to 100% (white)
const div = document.querySelector('#myDiv')
div.dataset.color = 100;
div.addEventListener('click', () => {
div.dataset.color = Math.max(0, parseInt(div.dataset.color, 10) - 5);
div.style.background = 'hsl(115, 100%, '+div.dataset.color+'%)';
})
#myDiv {
width: 200px;
height: 200px;
background: #FFF;
}
<div id="myDiv"></div>
To reverse the procedure at Question, begin at with an array with values set to [255, 255, 255], decrement value at indexes 0 and 2 by 5 at each click event until 0 is reached for value at each index 0 and 2, then decrement value at index 1 by 5 until 0 is reached.
When 0 is reached for r, g, b, increment g to 255, then increment r and b to 255, repeat.
const div = document.querySelector("#myDiv");
div.dataset.color = "[255, 255, 255]";
div.dataset.from = true;
const fromWhiteToGreenToBlackAndBack = () => {
let [r, g, b] = JSON.parse(div.dataset.color);
if (JSON.parse(div.dataset.from)) {
if (g !== 0) {
if (r > 0) {
r = b -= 5;
} else {
g -= 5;
}
} else {
div.dataset.from = false;
}
} else {
if (g < 255) {
g += 5;
} else {
r = b += 5;
}
if (r === 255) {
div.dataset.from = true;
}
}
div.dataset.color = JSON.stringify([r, g, b]);
div.style.background = `rgb(${r},${g},${b})`;
}
div.addEventListener("click", fromWhiteToGreenToBlackAndBack);
#myDiv {
width: 200px;
height: 200px;
background: #ffffff;
transition: 1s background ease;
}
<div id="myDiv"></div>

Categories

Resources