I'm forking Louis Hoebregts's brilliant Flowing Image on code pen, & attempting to get to modify it for my own art.
UPDATE: as suggested by commentators, I looked at the Chrome Dev console, it complains:
Fetch API cannot load file:///C:/Users/Sam/Downloads/flowing-imagehow-to/flowing-imagehow-to/dist/rowling-dark-bg.jpg. URL scheme "file" is not supported.
I tried removing dashes from the image file name but to no avail.
If I use a web URL that complains
Access to fetch at 'https://pottertour.co.uk/blog/images/rowling/rowling-dark-bg.jpg' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
but I want a relative file path anyway, I want to load an image locally. Any help pointing me to what I need to do appreciated.
JAVASCRIPT:
let img;
const detail = 6;
let particles = [];
let grid = [];
let particleImage;
let ctx;
function preload() {
img = loadImage('**https://pottertour.co.uk/blog/images/rowling/rowling-dark-bg.jpg**');
}
class Particle {
constructor (x, y) {
this.x = x || random(width);
this.y = y || random(height);
this.prevX = this.x;
this.speed = 0;
this.v = random(0, 0.7);
}
update (speed) {
if (grid.length) {
this.speed = grid[floor(this.y / detail)][floor(this.x / detail)] * 0.97;
}
this.x += (1 - this.speed) * 3 + this.v;
if (this.x > width) {
this.x = 0;
}
}
draw () {
image(particleImage, this.x, this.y);
}
}
/* ====== STEP 1 ====== */
function step1 () {
clear();
noLoop();
image(img, 0, 0, width, height);
noFill();
stroke(120);
strokeWeight(1);
strokeCap(SQUARE);
ctx.globalAlpha = 1;
for (let y = 0; y < height; y+=detail) {
for (let x = 0; x < width; x+=detail) {
rect(x + 0.5, y + 0.5, detail, detail);
}
}
}
...
function setup () {
const canvas = createCanvas(100,100);
ctx = canvas.drawingContext;
pixelDensity(1);
particleImage = createGraphics(8, 8);
particleImage.fill(255);
particleImage.noStroke();
particleImage.circle(4, 4, 4);
windowResized();
document.querySelector('#step').addEventListener('input', () => {
if (window['goToStep' + step.value]) {
window['goToStep' + step.value]();
}
draw();
});
}
function windowResized () {
const imgRatio = img.width/img.height;
if (windowWidth/windowHeight > imgRatio) {
resizeCanvas(floor(windowHeight * imgRatio), floor(windowHeight));
} else {
resizeCanvas(floor(windowWidth), floor(windowWidth / imgRatio));
}
noiseSeed(random(100));
if (window['goToStep' + step.value]) {
window['goToStep' + step.value]();
}
draw();
}
const texts = document.querySelectorAll('section p');
function draw () {
window['step' + step.value]();
texts.forEach(text => text.style.display = 'none');
texts[step.value - 1].style.display = 'block';
}
I tried downloading my fork and running it on my computer, under the assumption maybe Codepen doesn't like externally hosted image files, but it didn't work.
I think the problem is in the Javascript above. Probably in the setup function? Is there something there which is fussy about the dimensions of images that the thing loads? How would I fix that?
I do apologise, my Javascript is knowledge is presently cow headed, I just hack, Javascript is a holiday from App development for me.
HTML:
<input type="range" min="1" max="6" step="1" id="step" value="1">
<section>
<p>Draw an image and divide it into a grid</p>
<p>Get the brightness of every cell</p>
<p>Draw particles moving from left to right</p>
<p>Update each particle's speed based on the brightness of its position</p>
<p>Fade each particle based on its speed</p>
<p>Do not clear your scene on each frame, to let the particles fade out</p>
</section>
CSS
body {
margin: 0;
overflow: hidden;
display: flex;
height: 100vh;
background: black;
}
canvas {
margin: auto;
touch-action: none;
}
#mixin track {
box-sizing: border-box;
height: 6px;
background: #fff;
-webkit-appearance: none;
appearance: none;
}
#mixin thumb {
box-sizing: border-box;
width: 30px;
height: 30px;
border-radius: 50%;
background: #fff;
border: 2px solid black;
-webkit-appearance: none;
appearance: none;
cursor: grab;
}
input {
position: fixed;
left: 50%;
bottom: 20px;
transform: translateX(-50%);
width: 80%;
height: 34px;
max-width: 400px;
background: transparent;
-webkit-appearance: none;
appearance: none;
&:active {
cursor: grabbing;
}
&::-webkit-slider-runnable-track {#include track }
&::-moz-range-track { #include track }
&::-ms-track { #include track }
&::-webkit-slider-thumb {margin-top: -12px;#include thumb}
&::-moz-range-thumb { #include thumb }
&::-ms-thumb {margin-top:0px;#include thumb}
}
section {
box-sizing: border-box;
font-size: 30px;
color: white;
position: fixed;
left: 0;
top: 20px;
width: 100%;
text-align: center;
padding: 10px 10%;
z-index: 10;
pointer-events: none;
text-shadow: 0 0 3px black, 0 0 4px black, 0 0 5px black;
background: rgba(0, 0, 0, 0.7);
p {
margin: 0;
}
#media (max-width: 500px) {
font-size: 24px;
}
}
You could use the developer mode of Chrome to confirm whether there are exceptions, and debug the js.
Related
I made a canvas size as A4, i draw a rectangles inside and when i click the light blue rectangle it will call a function but the coordinates of the click on the rectangle is inconsistent and every row the y level is needed to click more lower then the light blue rectangle.
How i make the coordinates more accurate?
(Code need full screen)
'use strict';
function mmTopx(mm) {
//return mm * 3.7795275591;
//return mm * 3.7795275590551;
return (mm * 96) / 25.4;
}
function cmTopx(cm) {
return cm * 1; /* NEED TO FIX */
}
// Get mouse position
function getMousePos(c, evt) {
var rect = c.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top,
};
}
class sticker {
#width;
#height;
#fullheight;
constructor(width = 1, height = 1, fullheight = 1) {
if (!sticker.isNumber(width) || !sticker.isNumber(height) || !sticker.isNumber(fullheight)) throw 'Only accept number';
this.#width = width;
this.#height = height;
this.#fullheight = fullheight;
}
static isNumber(n) {
return !isNaN(parseFloat(n)) && !isNaN(n - 0) && typeof n != 'string';
}
get size() {
return { width: this.#width, height: this.#height, fullheight: this.#fullheight };
}
get width() {
return this.#width;
}
get height() {
return this.#height;
}
get fullheight() {
return this.#fullheight;
}
set width(n) {
if (!sticker.isNumber(n)) throw 'Only accept number';
this.#width = n;
}
set height(n) {
if (!sticker.isNumber(n)) throw 'Only accept number';
this.#height = n;
}
set fullheight(n) {
if (!sticker.isNumber(n)) throw 'Only accept number';
this.#fullheight = n;
}
}
window.onload = () => {
const controls = document.querySelectorAll('.control');
const canvas = document.querySelector('.A4');
const canvasPadding = {
left:
window
.getComputedStyle(canvas, null)
.getPropertyValue('padding-left')
.match(/\d+.\d+|\d+/)[0] - 0,
top:
window
.getComputedStyle(canvas, null)
.getPropertyValue('padding-top')
.match(/\d+.\d+|\d+/)[0] - 0,
};
/* ///////////////////////////////////////////////////// */
/* ///////////////////////////////////////////////////// */
/* ///// CANVAS ///// */
/* ///////////////////////////////////////////////////// */
/* canvas */
canvas.width = canvas.offsetWidth;
canvas.height = canvas.offsetHeight;
const ctx = canvas.getContext('2d');
const _sticker = new sticker(mmTopx(25), mmTopx(12), mmTopx(37));
let Stickers = [];
const drawSticker = (x, y, width, height, fullheight) => {
Stickers.push({
color: '#d8d8d8',
width: width,
height: height,
x: x,
y: y,
clicked: function () {
console.log(`you clicked me, ${this.x} ${this.y}`);
},
});
/* full area */
ctx.fillStyle = '#e8f2f8';
ctx.fillRect(x, y, width, fullheight); // x y width height
/* print area */
ctx.fillStyle = '#d8d8d8';
ctx.fillRect(x, y, width, height, fullheight); // x y width height
};
const render = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
Stickers = [];
const size = _sticker.size;
for (let y = 0; y < canvas.height - size.fullheight; y += size.fullheight + 10) {
for (let x = 10; x < canvas.width - size.width; x += size.width + 1) {
drawSticker(x, y, size.width, size.height, size.fullheight);
}
}
};
/* ///////////////////////////////////////////////////// */
/* ///////////////////////////////////////////////////// */
/* ///// Event Listeners ///// */
/* ///////////////////////////////////////////////////// */
canvas.addEventListener('updateRender', (event) => {
const lengthType = event.detail.dimensionValue.match(/mm|cm/)[0];
let value = event.detail.dimensionValue.match(/\d+/)[0] - 0;
switch (lengthType) {
case 'cm':
//value = cmTopx(value);
break;
case 'mm':
default:
value = mmTopx(value);
break;
}
/* Set the new sticker size */
switch (event.detail.dimension) {
case 'width':
_sticker.width = value;
break;
case 'height':
_sticker.height = value;
break;
case 'fullheight':
_sticker.fullheight = value;
break;
}
render();
});
const event = new Event('change');
controls.forEach((_control) => {
_control.addEventListener(
'change',
(event) => {
const value = event.target.value;
const name = event.target.name;
const dimension = event.target.getAttribute('data-sticker-dim');
const detail = {};
detail['dimension'] = dimension;
detail['dimensionValue'] = value;
detail['name'] = name;
const updateSticker = new CustomEvent('updateRender', { detail: detail });
canvas.dispatchEvent(updateSticker);
},
true
);
const input = _control.querySelector('input');
input.dispatchEvent(event);
});
/* click event for text input */
canvas.addEventListener('click', function (e) {
e.preventDefault();
let x = parseInt(e.clientX - canvas.offsetLeft - canvasPadding.left);
let y = parseInt(e.clientY - canvas.offsetTop - canvasPadding.top);
if (x >= 0 && y >= 0) {
for (let index = 0; index < Stickers.length; index++) {
const _sticker = Stickers[index];
if (
x >= _sticker.x &&
x <= Math.floor(_sticker.x + _sticker.width) &&
y >= _sticker.y &&
y <= Math.floor(_sticker.y + _sticker.height)
) {
_sticker.clicked();
break;
}
}
}
});
/* ///////////////////////////////////////////////////// */
};
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
background-color: #fafafa;
}
/* //////////////////////////////////////////////////////////// */
.container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-self: flex-start;
overflow: auto;
}
/* //////////////////////////////////////////////////////////// */
.container .controllers {
position: sticky;
top: 50%;
transform: translateY(-50%);
width: 250px;
height: 500px;
flex: 0 0 100px;
border-radius: 20px;
box-sizing: border-box;
padding: 20px;
margin-left: 5px;
border: 1px solid #9e9e9e;
}
.container .controllers .control {
position: relative;
flex: 0 0 100%;
height: 50px;
padding: 5px;
}
.container .controllers .control:first-child {
margin-top: 5px;
}
.container .controllers .control:not(:first-child) {
margin-top: 20px;
}
.container .controllers .control label::before {
content: attr(data-placeholder);
position: absolute;
left: 20px;
top: 20px;
color: #65657b;
font-family: sans-serif;
line-height: 14px;
pointer-events: none;
transform-origin: 0 50%;
transition: transform 200ms, color 200ms;
}
.container .controllers .control .cut {
position: absolute;
border-radius: 10px;
height: 20px;
width: fit-content;
left: 20px;
top: -20px;
transform: translateY(0);
transition: transform 200ms;
}
.container .controllers .control input {
height: 100%;
padding: 4px 20px 0;
width: 100%;
background-color: #eeeeee;
border-radius: 12px;
border: 0;
outline: 0;
box-sizing: border-box;
color: #eee;
font-size: 18px;
color: black;
}
.container .controllers .control input:focus ~ .cut,
.container .controllers .control input:not(:placeholder-shown) ~ .cut {
transform: translateY(8px);
}
.container .controllers .control input:focus ~ label::before,
.container .controllers .control input:not(:placeholder-shown) ~ label::before {
content: attr(data-focus-placeholder);
transform: translateY(-30px) translateX(10px) scale(0.75);
}
.container .controllers .control input:not(:placeholder-shown) ~ label::before {
content: attr(data-focus-placeholder);
color: #808097;
}
.container .controllers .control input:focus ~ label::before {
color: #dc2f55;
}
/* //////////////////////////////////////////////////////////// */
.container textarea {
background: none;
outline: none;
}
.container .A4 {
align-self: center;
width: 21cm;
flex: 0 0 29.7cm;
padding: 1cm;
border: 1px #d3d3d3 solid;
border-radius: 5px;
background: #f5f5f5;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.1);
}
/* //////////////////////////////////////////////////////////// */
#page {
size: A4;
margin: 0;
}
#media print {
html,
body {
height: 99%;
}
.page {
margin: 0;
border: initial;
border-radius: initial;
width: initial;
min-height: initial;
box-shadow: initial;
background: initial;
page-break-after: always;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>A4 Render Stickers</title>
</head>
<body>
<div class="container">
<div class="controllers">
<button>print</button>
<div class="control">
<input type="text" value="25mm" data-sticker-dim="width" name="printWidth" placeholder=" " />
<div class="cut"></div>
<label for="printWidth" data-focus-placeholder="Width print" data-placeholder="Width size of print area"></label>
</div>
<div class="control">
<input type="text" value="12mm" data-sticker-dim="height" name="printHeight" placeholder=" " />
<div class="cut"></div>
<label for="printHeight" data-focus-placeholder="Height print" data-placeholder="Height size of print area"></label>
</div>
<div class="control">
<input type="text" value="37mm" data-sticker-dim="fullheight" name="stikcerHeight" placeholder=" " />
<div class="cut"></div>
<label for="stikcerHeight" data-focus-placeholder="Full height sticker" data-placeholder="Height size of full sticker"></label>
</div>
</div>
<canvas width="800" height="800" class="A4"></canvas>
</div>
</body>
</html>
I have a small page. Divas in the form of circles are created here every certain time.
They spawn in random places.
As can be seen even on the buttons and slightly outside the page.
The question is. Is it possible to make a box that does not touch the buttons, and that the circles are created within this box?
This should be done as a border with a certain extension, but specifying everything in pixels is not an option, it will be bad for different screens.
I created such a frame, replaced document.body.appendChild(div);
on the document.getElementById("spawnRadius").appendChild(div);
It seems that they should appear within this frame, but no, all the same throughout the page.
I also tried instead of whole page height and width document.documentElement.clientWidth use the width and height of the desired border spawnRadius.width
But now all my circles do not appear randomly, but at the beginning of this block in one place.
I tried to see these values through console.log
console.log(documentHeight);
console.log(documentWidth);
But getting there undefined
PS. Demo watch in full page
//timer
var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var totalSeconds = 0;
setInterval(setTime, 1000);
function setTime() {
++totalSeconds;
secondsLabel.innerHTML = pad(totalSeconds % 60);
minutesLabel.innerHTML = pad(parseInt(totalSeconds / 60));
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
//create circle
var widthHeight = 65;
var margin = 25;
var delta = widthHeight + margin;
var spawnRadius = document.getElementById("spawnRadius");
let clicks = 0;
function createDiv(id, color) {
let div = document.createElement('div');
var currentTop = 0;
var documentHeight = spawnRadius.height;
var documentWidth = spawnRadius.width;
div.setAttribute('class', id);
if (color === undefined) {
let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
}
else {
div.style.borderColor = color;
}
div.classList.add("circle");
div.classList.add("animation");
currentTop = Math.floor(Math.random() * documentHeight) - delta;
currentLeft = Math.floor(Math.random() * documentWidth) - delta;
var limitedTop = Math.max(margin * -1, currentTop);
var limitedLeft = Math.max(margin * -1, currentLeft);
div.style.top = limitedTop + "px";
div.style.left = limitedLeft + "px";
const nodes = document.querySelectorAll('.animation');
for(let i = 0; i < nodes.length; i++) {
nodes[i].addEventListener('click', (event) => {
event.target.style.animation = 'Animation 200ms linear';
setTimeout(() => {
event.target.style.animation = '';
}, 220); });
}
$(div).click(function() {
$('#clicks').text(++clicks);
$(this).fadeOut();
});
document.getElementById("spawnRadius").appendChild(div);
}
let i = 0;
const oneSecond = 600;
setInterval(() => {
i += 1;
createDiv(`circle${i}`);
}, oneSecond);
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #0f0f0f;
}
.back {
font-family: "Comic Sans MS", cursive, sans-serif;
font-size: 25px;
letter-spacing: 2px;
word-spacing: 2px;
color: #ffffff;
text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
font-weight: 700;
text-decoration: none;
font-style: italic;
font-variant: normal;
text-transform: lowercase;
position: absolute;
top: 25%;
left: 2%;
user-select: none;
z-index: 999;
}
.panel {
color: #0f0f0f;
font-size: 40px;
z-index: 999;
position: absolute;
cursor: default;
user-select: none;
color: #0f0f0f;
}
.score {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.time {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.circle {
width: 60px;
height: 60px;
border-radius: 60px;
background-color: #0f0f0f;
border: 3px solid #000;
margin: 20px;
position: absolute;
}
#keyframes Animation {
0% {
transform: scale(1);
}
50% {
transform: scale(.8);
}
100% {
transform: scale(1);
}
}
#spawnRadius {
top: 55%;
height: 650px;
width: 1000px;
left: 50%;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);
background: #0f0f0f;
border: 2px solid #ebc6df;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
back
<div id="spawnRadius"></div>
To answer your main question, the getBoundingClientRect method can be used to retrieve the current bounding rectangle of an element, using which you can determine where the valid spawn areas are.
When choosing a valid placement, only consider the width and height of the container element, since the coordinates of child elements are relative to its parent. You also need to take into account the size of the elements being spawned, so the valid range of the x position for example is 0 to containerWidth - circleWidth.
The circles also had a CSS margin associated with them, which would offset them past their absolute coordinates.
There are a few other issues with the code though which you may run into later on:
There was an odd mix of jQuery and standard JavaScript calls, so if you're familiar with native JavaScript methods then it's likely simpler to stick with those and remove the dependency on jQuery.
For example, there were two click event handlers on each circle, one to add the CSS animation and another to increment the score. These can be combined into a single function.
The bounce out animation and the jQuery fade out can also be combined by adding opacity values into the animation start and end keyframes.
There was a loop in the createDiv function which added another click event handler to every circle element rather than just to the newly created element. This may have originally necessitated the jQuery click handler outside of that loop, since otherwise the score counter would have been incremented multiple times.
It was also possible to click the circles multiple times before the animation was complete (hence adding multiple points), which was likely not intended. Adding a simple Boolean clicked flag can avoid this.
Once the fade animation completed, the circle element itself was still on the page, it just had a display of none so wouldn't be visible. Over time, this would cause slowdowns on lower end hardware since there would be many DOM elements still sitting in memory that were no longer required. As such, it's best to remove elements from the DOM once they're no longer needed using removeChild. You had the right idea by removing the animation after the animation completed.
Here's the amended code:
var minutesLabel = document.getElementById("minutes");
var secondsLabel = document.getElementById("seconds");
var clickEl = document.getElementById("clicks");
var totalSeconds = 0;
let clicks = 0;
setInterval(setTime, 1000);
function setTime() {
++totalSeconds;
secondsLabel.innerText = pad(totalSeconds % 60);
minutesLabel.innerText = pad(parseInt(totalSeconds / 60));
}
function pad(val) {
var valString = val + "";
if (valString.length < 2) {
return "0" + valString;
} else {
return valString;
}
}
var spawnRadius = document.getElementById("spawnRadius");
var spawnArea = spawnRadius.getBoundingClientRect();
const circleSize = 66; // Including borders
//create circle
function createDiv(id, color) {
let div = document.createElement('div');
div.setAttribute('class', id);
if (color === undefined) {
let colors = ['#35def2', '#35f242', '#b2f235', '#f2ad35', '#f24735', '#3554f2', '#8535f2', '#eb35f2', '#f2359b', '#f23547'];
div.style.borderColor = colors[Math.floor(Math.random() * colors.length)];
}
else {
div.style.borderColor = color;
}
// Randomly position circle within spawn area
div.style.top = `${Math.floor(Math.random() * (spawnArea.height - circleSize))}px`;
div.style.left = `${Math.floor(Math.random() * (spawnArea.width - circleSize))}px`;
div.classList.add("circle", "animation");
// Add click handler
let clicked = false;
div.addEventListener('click', (event) => {
if (clicked) { return; } // Only allow one click per circle
clicked = true;
div.style.animation = 'Animation 200ms linear forwards';
setTimeout(() => { spawnRadius.removeChild(div); }, 220);
clickEl.innerText = ++clicks
});
spawnRadius.appendChild(div);
}
let i = 0;
const oneSecond = 1000;
setInterval(() => {
i += 1;
createDiv(`circle${i}`);
}, oneSecond);
html, body {
width: 100%;
height: 100%;
margin: 0;
background: #0f0f0f;
}
.back {
font-family: "Comic Sans MS", cursive, sans-serif;
font-size: 25px;
letter-spacing: 2px;
word-spacing: 2px;
color: #ffffff;
text-shadow: 0 0 5px #ffffff, 0 0 10px #ffffff, 0 0 20px #ffffff, 0 0 40px #ff00de, 0 0 80px #ff00de, 0 0 90px #ff00de, 0 0 100px #ff00de, 0 0 150px #ff00de;
font-weight: 700;
text-decoration: none;
font-style: italic;
font-variant: normal;
text-transform: lowercase;
position: absolute;
top: 25%;
left: 2%;
user-select: none;
z-index: 999;
}
.panel {
color: #0f0f0f;
font-size: 40px;
z-index: 999;
position: absolute;
cursor: default;
user-select: none;
color: #0f0f0f;
}
.score {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.time {
border: 1px solid #ffffff;
padding: 5px;
background-color: #ffffff;
border-radius: 40px 10px;
}
.circle {
width: 60px;
height: 60px;
border-radius: 60px;
background-color: #0f0f0f;
border: 3px solid #000;
position: absolute;
}
#keyframes Animation {
0% {
transform: scale(1);
opacity: 1;
}
50% {
transform: scale(.8);
}
100% {
transform: scale(1);
opacity: 0;
}
}
#spawnRadius {
top: 55%;
height: 650px;
width: 1000px;
left: 50%;
white-space: nowrap;
position: absolute;
transform: translate(-50%, -50%);
background: #0f0f0f;
border: 2px solid #ebc6df;
}
<span class="panel">
<span class="score">Score: <a id="clicks">0</a></span>
<span class="time">Time: <label id="minutes">00</label>:<label id="seconds">00</label></span>
</span>
back
<div id="spawnRadius"></div>
I am trying to make a signature pad but the JS (scribbling line) works in a developing sandbox but not when I combine the files. I have placed my CSS in between the head tags and script just before closing the body tags to allow the JS to run after the other components have run. Not sure what is causing it not to run. Beginner here, so I apologise if my question is too entry-level. Any assistance would be greatly appreciated.
<html>
<head>
<style>
*,
*::before,
*::after {
box-sizing: border-box;
}
body {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
height: 100vh;
width: 100%;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
margin: 0;
padding: 32px 16px;
font-family: Helvetica, Sans-Serif;
}
.signature-pad {
position: relative;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: normal;
-ms-flex-direction: column;
flex-direction: column;
font-size: 10px;
width: 100%;
height: 100%;
max-width: 700px;
max-height: 460px;
border: 1px solid #e8e8e8;
background-color: #fff;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.27), 0 0 40px rgba(0, 0, 0, 0.08) inset;
border-radius: 4px;
padding: 16px;
cursor: crosshair;
cursor: url('https://staging-dovetail.kinsta.cloud/wp-content/uploads/2021/05/Pen-Cursor-1.png') 53 53, crosshair;
}
}
.signature-pad::before,
.signature-pad::after {
position: absolute;
z-index: -1;
content: "";
width: 40%;
height: 10px;
bottom: 10px;
background: transparent;
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.4);
}
.signature-pad::before {
left: 20px;
-webkit-transform: skew(-3deg) rotate(-3deg);
transform: skew(-3deg) rotate(-3deg);
}
.signature-pad::after {
right: 20px;
-webkit-transform: skew(3deg) rotate(3deg);
transform: skew(3deg) rotate(3deg);
}
.signature-pad--body {
position: relative;
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
border: 1px solid #f4f4f4;
}
canvas {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
border-radius: 4px;
box-shadow: 0 0 5px rgba(0, 0, 0, 0.02) inset;
}
.signature-pad--footer {
color: #C3C3C3;
text-align: center;
font-size: 1.2em;
margin-top: 8px;
}
.signature-pad--actions {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-pack: justify;
-ms-flex-pack: justify;
justify-content: space-between;
margin-top: 8px;
}
#github img {
border: 0;
}
#fileupload {
display: none;
}
form {
display: table-row;
margin-right: 5px;
}
span[role=button] {
display: table-cell;
font-size: 1.2em;
}
span[role=button],
button {
cursor: pointer;
background-color: #e1e1e1;
color: #000000;
border: none;
padding: 8px;
margin-bottom: 8px;
}
#media (max-width: 940px) {
#github img {
width: 90px;
height: 90px;
}
}
</style>
</head>
<body>
<div id="signature-pad" class="signature-pad">
<div class="signature-pad--body">
<canvas>
Update your browser to support the canvas element!
</canvas>
</div>
<div class="signature-pad--footer">
<div class="description">Sign above</div>
<div class="signature-pad--actions">
<form action="#" enctype="multipart/form-data">
<label for="fileupload" id="buttonlabel">
</label>
<input type="file" id="fileupload" accept="image/*">
</form>
<div>
<button type="button" class="button clear" data-action="clear">Clear</button>
</div>
</div>
</div>
</div>
<script>
const wrapper = document.getElementById("signature-pad")
const clearButton = wrapper.querySelector("[data-action=clear]")
const changeColorButton = wrapper.querySelector("[data-action=change-color]")
const undoButton = wrapper.querySelector("[data-action=undo]")
const savePNGButton = wrapper.querySelector("[data-action=save-png]")
const saveJPGButton = wrapper.querySelector("[data-action=save-jpg]")
const saveSVGButton = wrapper.querySelector("[data-action=save-svg]")
const canvas = wrapper.querySelector("canvas")
const fileSelector = document.getElementById('fileupload')
// https://medium.com/the-everyday-developer/detect-file-mime-type-using-magic-numbers-and-javascript-16bc513d4e1e
const verifyAndSetPictureAsBackground = (event) => {
const file = event.target.files[0]
const fReader = new FileReader()
fReader.onloadend = (e) => {
if (e.target.readyState === FileReader.DONE) {
const uint = new Uint8Array(e.target.result)
let bytes = []
uint.forEach((byte) => bytes.push(byte.toString(16)))
const hex = bytes.join('').toUpperCase()
if (!(getMimeType(hex) === 'image/png' || getMimeType(hex) === 'image/gif' || getMimeType(hex) === 'image/jpeg')) {
alert('Please choose a picture(.png, .gif, or .jpeg)')
// https://stackoverflow.com/a/35323290/1904223
file = null
fileSelector.value = ''
if (!/safari/i.test(navigator.userAgent)) {
fileSelector.type = ''
fileSelector.type = 'file'
}
}
if (file) {
const dataURL = window.URL.createObjectURL(file)
signaturePad.fromDataURL(dataURL)
}
}
}
fReader.readAsArrayBuffer(file.slice(0, 4))
}
const getMimeType = (signature) => {
switch (signature) {
case '89504E47':
return 'image/png'
case '47494638':
return 'image/gif'
case 'FFD8FFDB':
case 'FFD8FFE0':
case 'FFD8FFE1':
return 'image/jpeg'
default:
return 'Not allowed filetype'
}
}
fileSelector.addEventListener('change', verifyAndSetPictureAsBackground, false)
const signaturePad = new SignaturePad(canvas, {
// It's Necessary to use an opaque color when saving image as JPEG
// this option can be omitted if only saving as PNG or SVG
backgroundColor: 'rgb(255, 255, 255)'
})
// Adjust canvas coordinate space taking into account pixel ratio,
// to make it look crisp on mobile devices.
// This also causes canvas to be cleared.
const resizeCanvas = () => {
// When zoomed out to less than 100%, for some very strange reason,
// some browsers report devicePixelRatio as less than 1
// and only part of the canvas is cleared then.
const ratio = Math.max(window.devicePixelRatio || 1, 1)
// This part causes the canvas to be cleared
canvas.width = canvas.offsetWidth * ratio
canvas.height = canvas.offsetHeight * ratio
canvas.getContext("2d").scale(ratio, ratio)
// This library does not listen for canvas changes, so after the canvas is automatically
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
// that the state of this library is consistent with visual state of the canvas, you
// have to clear it manually.
signaturePad.clear()
}
// On mobile devices it might make more sense to listen to orientation change,
// rather than window resize events.
window.onresize = resizeCanvas
resizeCanvas()
const download = (dataURL, filename) => {
const blob = dataURLToBlob(dataURL)
const url = window.URL.createObjectURL(blob)
const a = document.createElement("a")
a.style = "display: none"
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
window.URL.revokeObjectURL(url)
}
// One could simply use Canvas#toBlob method instead, but it's just to show
// that it can be done using result of SignaturePad#toDataURL.
function dataURLToBlob(dataURL) {
// Code taken from https://github.com/ebidel/filer.js
const parts = dataURL.split('base64,')
const contentType = parts[0].split(":")[1]
const raw = window.atob(parts[1])
const rawLength = raw.length
const uInt8Array = new Uint8Array(rawLength)
for (let i = 0; i < rawLength; ++i) {
uInt8Array[i] = raw.charCodeAt(i)
}
return new Blob([uInt8Array], { type: contentType })
}
clearButton.addEventListener("click", () => signaturePad.clear())
undoButton.addEventListener("click", () => {
const data = signaturePad.toData()
if (data) {
data.pop() // remove the last dot or line
signaturePad.fromData(data)
}
})
changeColorButton.addEventListener("click", () => {
const r = Math.round(Math.random() * 255)
const g = Math.round(Math.random() * 255)
const b = Math.round(Math.random() * 255)
const color = "rgb(" + r + "," + g + "," + b +")"
signaturePad.penColor = color
})
savePNGButton.addEventListener("click", () => {
if (signaturePad.isEmpty()) {
alert("Please provide a signature first.")
} else {
const dataURL = signaturePad.toDataURL()
download(dataURL, "signature.png")
}
})
saveJPGButton.addEventListener("click", () => {
if (signaturePad.isEmpty()) {
alert("Please provide a signature first.")
} else {
const dataURL = signaturePad.toDataURL("image/jpeg")
download(dataURL, "signature.jpg")
}
})
saveSVGButton.addEventListener("click", () => {
if (signaturePad.isEmpty()) {
alert("Please provide a signature first.")
} else {
const dataURL = signaturePad.toDataURL('image/svg+xml')
download(dataURL, "signature.svg")
}
})
</script>
</body>
</html>
When you open this file in the Browser you need to look into the "Developer Tools" to find the error in the console (If you don't know how to do that: try right-clicking on your webpage and select "inspect" from the context menu)
The console will show you:
Uncaught ReferenceError: SignaturePad is not defined
file:test.html:233
Looking in line 233 you find
const signaturePad = new SignaturePad(....
Where is the class SignaturePad defined? Did you maybe leave it in a separate file?
I have been trying to replicate some material design buttons but have run into an issue with the div that is generated to create the "ripple" effect. If you go to my codepen at https://codepen.io/AlexStiles/pen/oPomzX you will see the issue.
This is caused by the div (I tried deleting it and it fixed the problem). I have tried adding a variety of properties such as font-size and line-height to no avail. Interestingly, depending on your browser the issue seems to have a different effect. On safari the width increases hugely then it decreases to the chrome width.
"use strict";
const buttons = document.getElementsByTagName("button");
const overlay = document.getElementsByClassName("overlay");
const animationTime = 600;
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", createRipple);
};
let circle = document.createElement("div");
function createRipple(e) {
this.appendChild(circle);
var d = Math.max(this.scrollWidth, this.scrollHeight);
circle.style.width = circle.style.height = d + "px";
circle.style.left = e.clientX - this.offsetLeft - d / 2 + "px";
circle.style.top = e.clientY - this.offsetTop - d / 2 + "px";
circle.classList.add("ripple");
// setTimeout(function(){
// for (let i = 0; i < circle.length; i++)
// document.getElementsByClassName("ripple")[i].remove();
// }, animationTime);
}
button {
background-color: #4888f1;
border-radius: 24px;
display: flex;
align-items: center;
outline: 0;
border: 0;
padding: 10px 22px;
cursor: pointer;
overflow: hidden;
position: relative;
}
button .ripple {
position: absolute;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
transform: scale(0);
animation: ripple 0.5s linear;
font-size: 0;
line-height: 0;
}
#keyframes ripple {
to {
transform: scale(2.5);
opacity: 0;
}
}
button img {
width: 20px;
height: 20px;
}
button *:not(:last-child) {
margin: 0 8px 0 0;
}
button span {
color: #fff;
font-family: Futura;
}
#media screen and (min-width: 1280px) {
button {
padding: 0.8vw 1.75vw;
border-radius: 1.9vw;
} button img {
width: 1.55vw;
height: auto;
} button span {
font-size: 0.8vw;
}
}
<html>
<head>
<title>Material Design Components</title>
<link rel="stylesheet" href="style.css">
</head>
<button>
<span>Add to Cart</span>
</button>
<script src="js.js"></script>
</html>
Change
button *:not(:last-child) {
margin: 0 8px 0 0;
}
To,
button *:not(:last-child) {
margin: 0 0 0 0;
}
Checked in firefox.
When you add the ripple element you make it the last-child thus the rule of margin button *:not(:last-child) will apply to span since this one is no more the last child.
To fix this remove margin from the span:
"use strict";
const buttons = document.getElementsByTagName("button");
const overlay = document.getElementsByClassName("overlay");
const animationTime = 600;
for (let i = 0; i < buttons.length; i++) {
buttons[i].addEventListener("click", createRipple);
};
let circle = document.createElement("div");
function createRipple(e) {
this.appendChild(circle);
var d = Math.max(this.scrollWidth, this.scrollHeight);
circle.style.width = circle.style.height = d + "px";
circle.style.left = e.clientX - this.offsetLeft - d / 2 + "px";
circle.style.top = e.clientY - this.offsetTop - d / 2 + "px";
circle.classList.add("ripple");
// setTimeout(function(){
// for (let i = 0; i < circle.length; i++)
// document.getElementsByClassName("ripple")[i].remove();
// }, animationTime);
}
button {
background-color: #4888f1;
border-radius: 24px;
display: flex;
align-items: center;
outline: 0;
border: 0;
padding: 10px 22px;
cursor: pointer;
overflow: hidden;
position: relative;
}
button .ripple {
position: absolute;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.5);
transform: scale(0);
animation: ripple 0.5s linear;
font-size: 0;
line-height: 0;
}
#keyframes ripple {
to {
transform: scale(2.5);
opacity: 0;
}
}
button img {
width: 20px;
height: 20px;
}
button *:not(:last-child) {
margin: 0 8px 0 0;
}
button span:first-child {
color: #fff;
font-family: Futura;
margin:0;
}
#media screen and (min-width: 1280px) {
button {
padding: 0.8vw 1.75vw;
border-radius: 1.9vw;
} button img {
width: 1.55vw;
height: auto;
} button span {
font-size: 0.8vw;
}
}
<html>
<head>
<title>Material Design Components</title>
<link rel="stylesheet" href="style.css">
</head>
<button>
<span>Add to Cart</span>
</button>
<script src="js.js"></script>
</html>
I'm trying to get a JSFiddle example of http://trackingjs.com/examples/face_tag_friends.html working but the hover effect is not working as the website shows. Here's my JSFiddle:
https://jsfiddle.net/lolptdr/25yqfyjo/6/
I had to use a proxy on raw.githubusercontent.com and changed it to raw.githack.com for the external scripts referenced in the HTML to bypass the MIME type complaint. No other console errors so what else is wrong?
What else can I check to get the same effect as shown on trackingjs.com's website?
window.onload = function() {
var img = document.getElementById('img');
var tracker = new tracking.ObjectTracker('face');
tracking.track(img, tracker);
tracker.on('track', function(event) {
event.data.forEach(function(rect) {
plotRectangle(rect.x, rect.y, rect.width, rect.height);
});
});
var friends = ['Thomas Middleditch', 'Martin Starr', 'Zach Woods'];
var plotRectangle = function(x, y, w, h) {
var rect = document.createElement('div');
var arrow = document.createElement('div');
var input = document.createElement('input');
input.value = friends.pop();
rect.onclick = function name() {
input.select();
};
arrow.classList.add('arrow');
rect.classList.add('rect');
rect.appendChild(input);
rect.appendChild(arrow);
document.getElementById('photo').appendChild(rect);
rect.style.width = w + 'px';
rect.style.height = h + 'px';
rect.style.left = (img.offsetLeft + x) + 'px';
rect.style.top = (img.offsetTop + y) + 'px';
};
};
* {
margin: 0;
padding: 0;
font-family: Helvetica, Arial, sans-serif;
}
.demo-title {
position: absolute;
width: 100%;
background: #2e2f33;
z-index: 2;
padding: .7em 0;
}
.demo-title a {
color: #fff;
border-bottom: 1px dotted #a64ceb;
text-decoration: none;
}
.demo-title p {
color: #fff;
text-align: center;
text-transform: lowercase;
font-size: 15px;
}
.demo-frame {
background: url(frame.png) no-repeat;
width: 854px;
height: 658px;
position: fixed;
top: 50%;
left: 50%;
margin: -329px 0 0 -429px;
padding: 95px 20px 45px 34px;
overflow: hidden;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
box-sizing: border-box;
}
.demo-container {
width: 100%;
height: 530px;
position: relative;
background: #eee;
overflow: hidden;
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
}
.dg.ac {
z-index: 100 !important;
top: 50px !important;
}
/* example's CSS */
#photo:hover .rect {
opacity: .75;
transition: opacity .75s ease-out;
}
.rect:hover * {
opacity: 1;
}
.rect {
border-radius: 2px;
border: 3px solid white;
box-shadow: 0 16px 28px 0 rgba(0, 0, 0, 0.3);
cursor: pointer;
left: -1000px;
opacity: 0;
position: absolute;
top: -1000px;
}
.arrow {
border-bottom: 10px solid white;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
height: 0;
width: 0;
position: absolute;
left: 50%;
margin-left: -5px;
bottom: -12px;
opacity: 0;
}
input {
border: 0px;
bottom: -42px;
color: #a64ceb;
font-size: 15px;
height: 30px;
left: 50%;
margin-left: -90px;
opacity: 0;
outline: none;
position: absolute;
text-align: center;
width: 180px;
transition: opacity .35s ease-out;
}
#img {
position: absolute;
top: 50%;
left: 50%;
margin: -173px 0 0 -300px;
}
<script src="https://raw.githack.com/eduardolundgren/tracking.js/master/build/tracking.js"></script>
<script src="https://raw.githack.com/eduardolundgren/tracking.js/master/build/data/face.js"></script>
<div class="demo-title">
<p>tracking.js - hover image to see all faces detected</p>
</div>
<div class="demo-frame">
<div class="demo-container"> <span id="photo"><img id="img" src="https://raw.githubusercontent.com/eduardolundgren/tracking.js/master/examples/assets/faces.jpg" /></span>
</div>
</div>
All the above answers address why this is failing very well, but here's a working example of tracker.js in jsfiddle using a flickr image:
http://jsfiddle.net/rambutan2000/v5v49bax/
Flickr seems to have Access-Control-Allow-Origin set correctly in the header. I've had limited success using a proxy (crossorigin.me).
Thi is a simplified version of this example:
https://trackingjs.com/examples/face_hello_world.html
First I had to get valid URLs to the Tracker includes, I used this service:
http://rawgit.com. Look in the "External Resources" in jsfiddle.
I based this off an example which uses a XMLHttpRequest to retrieve the image data as a buffer, then load this into an img element. This negates SOME CORS issues on the img element as it was sourced from code not a URL. The rest is ripped straight from the Tracker example referenced above.
JS:
// use http://rawgit.com/ to get js urls from github
// use https://crossorigin.me/ to get around CORS for image reference
function _arrayBufferToBase64(buffer) {
var binary = ''
var bytes = new Uint8Array(buffer)
var len = bytes.byteLength;
for (var i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i])
}
return window.btoa(binary);
}
window.plot = function(x, y, w, h) {
var $rect = $("<div></div>");
$rect.addClass("rect");
$rect.offset({ top: y, left: x });
$rect.width(w).height(h);
$("#demo-container").append($rect);
};
var imgURL = 'https://c1.staticflickr.com/4/3943/15715482121_d7120a6e0b_z.jpg'; // Works!
//var imgURL = 'https://placeimg.com/640/480/people'; // Does not work
//var imgURL = 'https://crossorigin.me/https://placeimg.com/640/480/people'; // Works!
var oReq = new XMLHttpRequest();
oReq.open("GET", imgURL, true);
oReq.responseType = "arraybuffer";
oReq.onload = function (oEvent) {
var arrayBuffer = oReq.response; // Note: not oReq.responseText
if (arrayBuffer) {
var x = imgURL.split('.');
var ext = x[x.length - 1];
var b64img = _arrayBufferToBase64(arrayBuffer);
$("#img").attr('src', 'data:image/' + ext + ';base64,' + b64img).appendTo($('body'));
var img = document.getElementById('img');
var tracker = new tracking.ObjectTracker(['face']);
tracker.setStepSize(1.7);
tracking.track('#img', tracker);
tracker.on('track', function(event) {
event.data.forEach(function(rect) {
console.log(rect);
window.plot(rect.x, rect.y, rect.width, rect.height);
});
});
}
};
oReq.send(null);
HTML:
<div id="demo-container">
<img id="img" src="" />
</div>
CSS:
.rect {
position:absolute;
border-style: solid;
border-width: 2px;
border-color: blue;
}
#demo-container {
position:absolute;
}
This is a quick answer, but hopefully it'll help you understand what's going on. It's failing because the code is trying to load this resource: http://trackingjs.com/bower/tracking.js/examples/assets/frame.png
You can see that it's loaded on the original page: http://trackingjs.com/examples/face_tag_friends.html and you can see that your JSFiddle attempts to load it as well (although with a different host, the same relative path). When the browser attempts to GET https://fiddle.jshell.net/25yqfyjo/7/show/frame.png, a 404 happens because the file doesn't exist and this halts execution.
Look at your Developer Tools while running the JSFiddle. My guess is, that this other script you load (https://raw.githack.com/eduardolundgren/tracking.js/master/build/data/face.js), which appears to be binary data (rendering the picture) shouldn't be included. Instead, walk through the base documentation for http://trackingjs.com/ and understand how to use face detection on your own photo. Presumably, it'll be easier and work.
Cross Origin:
i have updated your code to use DOMContentLoaded and manually firing the event so you can see the problem: https://jsfiddle.net/25yqfyjo/11/
So i'm forcing the event with
// Create the event
var event = new CustomEvent("DOMContentLoaded", { "detail": "Content Loaded trigger" });
// Dispatch/Trigger/Fire the event
document.dispatchEvent(event);
And you can see in the console the error:
Uncaught SecurityError: Failed to execute 'getImageData' on
'CanvasRenderingContext2D': The canvas has been tainted by
cross-origin
data.tracking.trackCanvasInternal_ #
tracking.js:196(anonymous function) #
tracking.js:221img.onload #
tracking.js:472
This is caused by the fact your loading an image from another URL into the canvas that is being used by your code (even though you can't see it the var tracker = new tracking.ObjectTracker('face'); will be using a canvas) i think for purposes of JS Fiddle you could change the Image to Base64 encoded and that would correct the problem.
You will need more actions :
First open developer tool F12 (chrome) goto tab console switch frame to :
Now you can track your code . I found your js code looks like your use of
window.onload is little critical in any way .(tag breaks) . You have more times in code onload event.
Every time when you call window.onload = SOMETHING you are override this function . window.onload is function with just one time execution . Simply on load document . This is not JQ .
Also you have :
GET https://fiddle.jshell.net/lolptdr/25yqfyjo/6/show/frame.png 404 (Not Found)
This is namespace { } for js ,
if you have error like this .
{
exeOK()
IamError() ; BREAKS HERE
IneverLoaded()
}
This can be also your solution :
Check in debugger did you load js code to the end.