Text is vertically stretched on my canvas, making it unreadable - javascript

I have a canvas that I am drawing on top of a OpenLayers map. The canvas is colored in gradient colors and now I am trying to add text on top of it.
However the text seems to be stretched making it completely unreadable.
function createSpeedBar(min, max, speeds) {
//List of integer speeds
var fullSpeeds = [];
//List of unique speed values (no duplicates)
var uniqueSpeeds = [];
//Filling fullSpeeds using minimum and maximum values
for (i = min; i <= max; i++) {
fullSpeeds.push(i);
}
//Filling uniqueSpeeds with unique values
$.each(speeds, function (i, el) {
if ($.inArray(el, uniqueSpeeds) === -1) uniqueSpeeds.push(el);
});
//Sorting uniqueSpeeds (low to high)
uniqueSpeeds = uniqueSpeeds.sort(function (a, b) {
return a - b;
});
//Getting canvas element
var canvas = document.getElementById("speedList");
var context = canvas.getContext("2d");
context.rect(0, 0, canvas.width, canvas.height);
var grd = context.createLinearGradient(0, 0, 0, 170);
var hslMin = 0;
var hslMax = 240;
//Defining hslColors using uniqueSpeeds
var hslValues = uniqueSpeeds.map(function (value) {
if ($.inArray(value, fullSpeeds)){
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: true,
speed: value
};
} else {
return {
h: Math.ceil(((value - min) / (max - min)) * (hslMax - hslMin) + hslMin),
s: 100,
l: 50,
full: false
};
};
});
var count = 1;
var length = hslValues.length;
//Gradient coloring using hslColors
hslValues.forEach(function (value) {
var color = 'hsl(' + value.h + ',' + value.s + '%,' + value.l + '%)';
grd.addColorStop(count / length, color)
count += 1;
});
context.fillStyle = grd;
context.fill();
//Setting up coloring and drawing of text
count = 1
var height = canvas.height;
var width = canvas.width;
var elementHeight = height / length;
//Drawing text on canvas
hslValues.forEach(function (value) {
context.font = "12px Arial";
context.textAlign = "center";
context.textBaseline = "middle";
context.fillStyle = "black";
if (value.full === true) {
context.fillText(value.speed, width / 2, ((elementHeight / 2) + elementHeight * count));
}
count += 1;
});
}
As it might be clear I am trying to create a bar displaying the intensities of the speed on the map where I have colored some markers. However the text on the canvas comes out like this:
Right now I have made the height of the canvas inherit the height of the map which is 500. The width of the canvas is set manually using css:
html
<div id="map" class="map">
<canvas id="speedList"></canvas>
</div>
css
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
width: 20px;
z-index: 1000;
height: inherit;
margin: 0;
padding: 0;
}
I am currently working on a Fiddle, but it takes a little time to reproduce, and I bet the issue is not that big, so hopefully someone knows how to fix it before I finish the Fiddle.

The main problem here is the CSS adjustment of the width and height of the canvas element.
If it helps to understand the problem, think of <canvas> the same way you would think of <img/>, if you take an img, and give it a width and height of 50 x 500, it would stretch too.
The fix is to ensure that you set the width an height of the canvas element itself, before it processes it's content, like this:
<canvas id="speedList" width="20" height="500"></canvas>
You then also need to make sure your remove the width and height properties inside your CSS, like this:
#map {
position: relative;
height: 500px;
}
#speedList {
position: absolute;
right: 10px;
top: 0;
bottom: 0;
z-index: 1000;
margin: 0;
padding: 0;
}

Related

How to make the point to be spawned have the same coordinates to the "left" or "top" as the previous point

I need to make it so that it is possible to connect the dice only with straight lines. But to do this, I need each subsequent dice to have a common "top" or "left" coordinate with the previous dice and at the same time a partial random is preserved.
Here is an example:
Here is my code:
<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>Игральные кости</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" type="text/css" href="./style.css">
<link rel="icon" type="image/png" href="./dice.png">
</head>
<body>
<div id="main-container">
<div id="square-container">
</div>
<canvas id="overlay"></canvas>
</div>
</body>
<script src="./script.js"></script>
</html>
html,
body {
margin: 0;
padding: 0;
}
#main-container {
width: 100%;
margin: 10px
}
#square-container {
width: 700px;
height: 600px;
position: absolute;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
background: #dddddd;
border: 5px solid #6b6b6b;
border-radius: 5px;
z-index: 1;
}
#overlay {
width: 100%;
height: 100%;
pointer-events: none;
z-index: 9999;
position: absolute;
top: 0;
left: 0;
}
.js-is-target {
position: absolute;
width: 56px;
height: 56px;
/*background: blue;*/
border-radius: 30px;
}
.square {
top: 13px;
left: 13px;
position: relative;
width: 30px;
height: 30px;
background: red;
background-size: contain;
border-radius: 5px;
z-index: 10;
}
.active-target {
box-shadow: 0 0 0 2px rgba(255, 0, 0, 0.5);
}
//links to images of dice
var imgArr = [
'./dotsOnDice/dice1.jpg',
'./dotsOnDice/dice2.jpg',
'./dotsOnDice/dice3.jpg',
'./dotsOnDice/dice4.jpg',
'./dotsOnDice/dice5.jpg',
'./dotsOnDice/dice6.jpg'
]
//random number from 3 to 6
var randomAmount = Math.floor(Math.random() * (6 - 3 + 1)) + 3;
//limit for recursion
var limitDice = 0;
function addDice() {
// if the limit is equal to a random number, the recursion function is interrupted
if (limitDice === randomAmount) {
return
}
//random number from 2 to 5
let randomAmountImg = Math.floor(Math.random() * (5 - 2 + 1)) + 2;
//random numbers for dice coordinates
let position = {
top: Math.random() * 520,
left: Math.random() * 600
}
//saved previous coordinates for comparison
let positionAfter = {
top: 0,
left: 0
}
//creating an id that we press later
let idSquare = 'idSquare-' + limitDice;
//subtract the previous coordinates from the current coordinates
let top = position.top - positionAfter.top;
let left = position.left - positionAfter.left;
//we compare the positions of the previous coordinates to avoid a large number of overlaps
if (top > 50 || left > 50) {
positionAfter.top = position.top;
positionAfter.left = position.left;
//adding dice to the playing field
let square = $('<div class="js-is-target"><div id="' + idSquare + '" class="square"></div></div>');
square.appendTo('#square-container').css({ left: position.left + 'px', top: position.top + 'px' });
if (limitDice === 0) {
$('#idSquare-0').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 1) {
$('#idSquare-1').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 2) {
$('#idSquare-2').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 3) {
$('#idSquare-3').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 4) {
$('#idSquare-4').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
} else if (limitDice === 5) {
$('#idSquare-5').css('background-image', 'url(' + imgArr[randomAmountImg] + ')');
}
limitDice += 1;
}
addDice();
}
addDice();
//here we store the canvas field
const canvas = document.getElementById('overlay');
//here we set the format in 2D for canvas
const ctx = canvas.getContext('2d');
//here we store the width and height of the canvas field. We set the height and width in HTML
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
//in the variable we store the element that we clicked on
let targetEl = null;
//in the variable we store the coordinates of the center of the element we clicked on
let targetCenter = null;
//we check whether you clicked on the mouse or not
var down = false;
//coordinates of the pressed cursor mouseCoords
var mouseCoords = {
x: 0,
y: 0
}
//coordinates of the starting point
var centerCoords = {
x: 760,
y: 280
}
//в этой функции мы вычисляем где центр у прямоугольника
function getCenter(el) {
//if not true, the function terminates and returns null
if (!el) return null;
//we save the coordinates in the rect constant using the getBoundingClientRect method
const rect = el.getBoundingClientRect();
//returning the x and y coordinates. Here we calculate the center of the rectangle
return {
x: rect.left + rect.width / 2 + window.scrollX,
y: rect.top + rect.height / 2 + window.scrollY,
}
}
//connection points
function joinPoints(ctx, from, to) {
//dotted line size
const dashSize = 10;
//draw a dotted line and write the size in the arguments
ctx.setLineDash([dashSize]);
//setting the gradient
var stroke = ctx.createLinearGradient(from.x, from.y, to.x, to.y);
//setting the color and position
stroke.addColorStop(0, 'blue');
stroke.addColorStop(1, 'red');
//passing the colors to the strokeStyle property to set the color
ctx.strokeStyle = stroke;
//line thickness
ctx.lineWidth = 5;
//draw a contour (line)
ctx.beginPath();
//moves the contour point to the specified coordinates without drawing a line. Starting point
ctx.moveTo(from.x, from.y);
//adds a new contour point and creates a line to this point from the last specified point. End point
ctx.lineTo(to.x, to.y);
//draw a line
ctx.stroke();
}
//when you click the mouse
$(document).mousedown(function (e) {
down = true;
mouseCoords.x = e.pageX;
mouseCoords.y = e.pageY;
});
//when moving the mouse
$(document).mousemove(function (event) {
if (down) {
mouseCoords.x = event.pageX;
mouseCoords.y = event.pageY;
console.log(mouseCoords.x + ' ' + mouseCoords.y);
}
});
//when we release the mouse button
$(document).mouseup(function (e) {
down = false;
});
$(".js-is-target").hover(function (e) {
if (down) {
down = false;
//we transmit data about which js-is-target we clicked on
targetEl = e.target;
//we write down where the center of the object we clicked on is. We calculate using the getCenter() function
targetCenter = getCenter(targetEl);
//changing the coordinates of the starting point
centerCoords.x = targetCenter.x;
centerCoords.y = targetCenter.y;
}
});
//first we count, then we draw a line from ctx.lineTo to ctx.moveTo
function drwaDot() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = 'red';
ctx.moveTo(mouseCoords.x, mouseCoords.y);
ctx.lineTo(centerCoords.x, centerCoords.y);
ctx.stroke();
};
//the drawing interval of our line
var intervalId = setInterval(function () {
if (down) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
joinPoints(ctx, { x: centerCoords.x, y: centerCoords.y }, mouseCoords);
} else {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
}, 10);
jsfiddle link
under the comment "//we compare the positions of the previous coordinates to avoid a large number of overlaps" changed the condition:
if(top < 10 || left < 10){
dice spawn code
}
That's what came out of it:

HTML5 Canvas pixellation issue on Safari (blurry) [duplicate]

Running into an interesting canvas bug: after translating a canvas, the pixels appear blurry on Safari but not Chrome.
I've tried just about every image-rendering and imageSmoothing trick to no avail.
Here's a codepen where I've been able to reproduce the issue: https://codepen.io/plillian/pen/RwQegyR
Is this a just Safari bug? Or is there a way to force nearest neighbor in Safari as well?
Yes this is a Safari bug, you may want to let them know about it. For what it's worth, it's still an issue in the latest Technology Preview (Safari 15.4, WebKit 17614.1.14.10.6) where it's not even able to render every frame on time and will just "blink".
As for a workaround, the only one I can think of would be to do this all on the canvas directly, you can easily make this resizing of an ImageData by first converting it to an ImageBitmap and use drawImage().
Though to implement the scrolling behavior we'll have a bit of work to do.
One way is to use a placeholder <div> and make it act as-if we did transform our <canvas>. This way we can still use the native scrolling behavior and simply update the arguments to drawImage().
We then can stick the canvas on the top left corner of the viewport, and set it to the size of the viewport, overcoming the issue of possibly having a too big canvas.
(async () => {
const SIZE = 1024;
const X = -208.97398878415459;
const Y = 47.03519866364394;
const scale = 80;
const viewport = document.getElementById('viewport');
const wrapper = document.getElementById('wrapper');
const placeholder = document.getElementById('placeholder');
const canvas = document.getElementById('cvs');
placeholder.style.width = SIZE + 'px';
placeholder.style.height = SIZE + 'px';
const c = canvas.getContext('2d');
const pixels = new Uint8ClampedArray(4 * SIZE * SIZE);
for (let xi = 0; xi < SIZE; xi++) {
for (let yi = 0; yi < SIZE; yi++) {
const idx = (xi + yi * SIZE) * 4;
pixels[idx] = (xi << 6) % 255;
pixels[idx + 1] = (yi << 6) % 255;
pixels[idx + 3] = 255;
}
}
const pixelData = new ImageData(pixels, SIZE, SIZE);
// Convert to an ImageBitmap for ease of resizing and cropping
const bmp = await createImageBitmap(pixelData);
// We resize the canvas bitmap based on the size of the viewport
// While respecting the actual dPR (gimme crisp pixels!)
// Thanks to gman for the reminder of how to suppport all early impl.
// https://stackoverflow.com/a/65435847/3702797
const observer = new ResizeObserver(([entry]) => {
let width;
let height;
const dPR = devicePixelRatio;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else if (entry.contentBoxSize) {
if ( entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize * dPR;
height = entry.contentBoxSize[0].blockSize * dPR;
} else {
width = entry.contentBoxSize.inlineSize * dPR;
height = entry.contentBoxSize.blockSize * dPR;
}
} else {
width = entry.contentRect.width * dPR;
height = entry.contentRect.height * dPR;
}
canvas.width = width;
canvas.height = height;
canvas.style.width = (width / dPR) + 'px';
canvas.style.height = (height / dPR) + 'px';
c.scale(dPR, dPR);
c.imageSmoothingEnabled = false;
});
// observe the scrollbox size changes
try {
observer.observe(viewport, { box: 'device-pixel-content-box' });
}
catch(err) {
observer.observe(viewport, { box: 'content-box' });
}
function getDrawImageArgs(nodetranslate) {
const { width, height } = canvas;
const { scrollLeft, scrollTop } = viewport;
const mat = new DOMMatrix(nodetranslate).inverse();
const source = mat.transformPoint({ x: scrollLeft, y: scrollTop });
const sourceWidth = canvas.width;
const sourceHeight = canvas.height;
return [source.x, source.y, sourceWidth, sourceHeight, 0, 0, canvas.width * scale, canvas.height * scale];
}
function animate() {
const nodetranslate = `translate3D(${X}px, ${Y}px, 0px) scale(${scale})`;
wrapper.style.transform = nodetranslate;
c.clearRect(0, 0, canvas.width, canvas.height);
c.drawImage(bmp, ...getDrawImageArgs(nodetranslate));
requestAnimationFrame(animate);
}
animate();
})().catch(console.error)
body { margin: 0 }
#viewport {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: auto;
}
.sticker {
position: sticky;
top: 0;
left: 0;
height: 0px;
width: 0px;
overflow: visible;
line-height: 0;
z-index: 1;
}
canvas {
position: absolute;
}
#wrapper {
transform-origin: 0 0;
position: absolute;
}
#placeholder {
display: inline-block;
}
<script>
// Because Safari wouldn't be Safari without all its little bugs...
// See https://stackoverflow.com/a/35503829/3702797
(()=>{if(function(){const e=document.createElement("canvas").getContext("2d");e.fillRect(0,0,40,40),e.drawImage(e.canvas,-40,-40,80,80,50,50,20,20);var a=e.getImageData(50,50,30,30),r=new Uint32Array(a.data.buffer),n=(e,t)=>r[t*a.width+e];return[[9,9],[20,9],[9,20],[20,20]].some(([e,t])=>0!==n(e,t))||[[10,10],[19,10],[10,19],[19,19]].some(([e,t])=>0===n(e,t))}()){const e=CanvasRenderingContext2D.prototype,i=e.drawImage;i?e.drawImage=function(e,t,a){if(!(9===arguments.length))return i.apply(this,[...arguments]);var r,n=function(e,t,a,r,n,i,o,h,m){var{width:s,height:d}=function(t){var e=e=>{e=globalThis[e];return e&&t instanceof e};{if(e("HTMLImageElement"))return{width:t.naturalWidth,height:t.naturalHeight};if(e("HTMLVideoElement"))return{width:t.videoWidth,height:t.videoHeight};if(e("SVGImageElement"))throw new TypeError("SVGImageElement isn't yet supported as source image.","UnsupportedError");return e("HTMLCanvasElement")||e("ImageBitmap")?t:void 0}}(e);r<0&&(t+=r,r=Math.abs(r));n<0&&(a+=n,n=Math.abs(n));h<0&&(i+=h,h=Math.abs(h));m<0&&(o+=m,m=Math.abs(m));var g=Math.max(t,0),u=Math.min(t+r,s),s=Math.max(a,0),d=Math.min(a+n,d),r=h/r,n=m/n;return[e,g,s,u-g,d-s,t<0?i-t*r:i,a<0?o-a*n:o,(u-g)*r,(d-s)*n]}(...arguments);return r=n,[3,4,7,8].some(e=>!r[e])?void 0:i.apply(this,n)}:console.error("This script requires a basic implementation of drawImage")}})();
</script>
<div id="viewport">
<div class="sticker">
<!-- <canvas> isn't a void element, it must have a closing tag -->
<!-- We place it in a "sticky" element, outside of the one that gets transformed -->
<canvas id="cvs"></canvas>
</div>
<div id="wrapper">
<div id="placeholder"><!--
We'll use it as an easy way to measure what part of the canvas we should draw
based on the current scroll position.
--></div>
<div>
</div>
You can inspect the <canvas> element and see it's actually only as big as the viewport and not some 81920x81920px.

HTML Canvas blurry in Safari but not Chrome after translate3d

Running into an interesting canvas bug: after translating a canvas, the pixels appear blurry on Safari but not Chrome.
I've tried just about every image-rendering and imageSmoothing trick to no avail.
Here's a codepen where I've been able to reproduce the issue: https://codepen.io/plillian/pen/RwQegyR
Is this a just Safari bug? Or is there a way to force nearest neighbor in Safari as well?
Yes this is a Safari bug, you may want to let them know about it. For what it's worth, it's still an issue in the latest Technology Preview (Safari 15.4, WebKit 17614.1.14.10.6) where it's not even able to render every frame on time and will just "blink".
As for a workaround, the only one I can think of would be to do this all on the canvas directly, you can easily make this resizing of an ImageData by first converting it to an ImageBitmap and use drawImage().
Though to implement the scrolling behavior we'll have a bit of work to do.
One way is to use a placeholder <div> and make it act as-if we did transform our <canvas>. This way we can still use the native scrolling behavior and simply update the arguments to drawImage().
We then can stick the canvas on the top left corner of the viewport, and set it to the size of the viewport, overcoming the issue of possibly having a too big canvas.
(async () => {
const SIZE = 1024;
const X = -208.97398878415459;
const Y = 47.03519866364394;
const scale = 80;
const viewport = document.getElementById('viewport');
const wrapper = document.getElementById('wrapper');
const placeholder = document.getElementById('placeholder');
const canvas = document.getElementById('cvs');
placeholder.style.width = SIZE + 'px';
placeholder.style.height = SIZE + 'px';
const c = canvas.getContext('2d');
const pixels = new Uint8ClampedArray(4 * SIZE * SIZE);
for (let xi = 0; xi < SIZE; xi++) {
for (let yi = 0; yi < SIZE; yi++) {
const idx = (xi + yi * SIZE) * 4;
pixels[idx] = (xi << 6) % 255;
pixels[idx + 1] = (yi << 6) % 255;
pixels[idx + 3] = 255;
}
}
const pixelData = new ImageData(pixels, SIZE, SIZE);
// Convert to an ImageBitmap for ease of resizing and cropping
const bmp = await createImageBitmap(pixelData);
// We resize the canvas bitmap based on the size of the viewport
// While respecting the actual dPR (gimme crisp pixels!)
// Thanks to gman for the reminder of how to suppport all early impl.
// https://stackoverflow.com/a/65435847/3702797
const observer = new ResizeObserver(([entry]) => {
let width;
let height;
const dPR = devicePixelRatio;
if (entry.devicePixelContentBoxSize) {
width = entry.devicePixelContentBoxSize[0].inlineSize;
height = entry.devicePixelContentBoxSize[0].blockSize;
} else if (entry.contentBoxSize) {
if ( entry.contentBoxSize[0]) {
width = entry.contentBoxSize[0].inlineSize * dPR;
height = entry.contentBoxSize[0].blockSize * dPR;
} else {
width = entry.contentBoxSize.inlineSize * dPR;
height = entry.contentBoxSize.blockSize * dPR;
}
} else {
width = entry.contentRect.width * dPR;
height = entry.contentRect.height * dPR;
}
canvas.width = width;
canvas.height = height;
canvas.style.width = (width / dPR) + 'px';
canvas.style.height = (height / dPR) + 'px';
c.scale(dPR, dPR);
c.imageSmoothingEnabled = false;
});
// observe the scrollbox size changes
try {
observer.observe(viewport, { box: 'device-pixel-content-box' });
}
catch(err) {
observer.observe(viewport, { box: 'content-box' });
}
function getDrawImageArgs(nodetranslate) {
const { width, height } = canvas;
const { scrollLeft, scrollTop } = viewport;
const mat = new DOMMatrix(nodetranslate).inverse();
const source = mat.transformPoint({ x: scrollLeft, y: scrollTop });
const sourceWidth = canvas.width;
const sourceHeight = canvas.height;
return [source.x, source.y, sourceWidth, sourceHeight, 0, 0, canvas.width * scale, canvas.height * scale];
}
function animate() {
const nodetranslate = `translate3D(${X}px, ${Y}px, 0px) scale(${scale})`;
wrapper.style.transform = nodetranslate;
c.clearRect(0, 0, canvas.width, canvas.height);
c.drawImage(bmp, ...getDrawImageArgs(nodetranslate));
requestAnimationFrame(animate);
}
animate();
})().catch(console.error)
body { margin: 0 }
#viewport {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
overflow: auto;
}
.sticker {
position: sticky;
top: 0;
left: 0;
height: 0px;
width: 0px;
overflow: visible;
line-height: 0;
z-index: 1;
}
canvas {
position: absolute;
}
#wrapper {
transform-origin: 0 0;
position: absolute;
}
#placeholder {
display: inline-block;
}
<script>
// Because Safari wouldn't be Safari without all its little bugs...
// See https://stackoverflow.com/a/35503829/3702797
(()=>{if(function(){const e=document.createElement("canvas").getContext("2d");e.fillRect(0,0,40,40),e.drawImage(e.canvas,-40,-40,80,80,50,50,20,20);var a=e.getImageData(50,50,30,30),r=new Uint32Array(a.data.buffer),n=(e,t)=>r[t*a.width+e];return[[9,9],[20,9],[9,20],[20,20]].some(([e,t])=>0!==n(e,t))||[[10,10],[19,10],[10,19],[19,19]].some(([e,t])=>0===n(e,t))}()){const e=CanvasRenderingContext2D.prototype,i=e.drawImage;i?e.drawImage=function(e,t,a){if(!(9===arguments.length))return i.apply(this,[...arguments]);var r,n=function(e,t,a,r,n,i,o,h,m){var{width:s,height:d}=function(t){var e=e=>{e=globalThis[e];return e&&t instanceof e};{if(e("HTMLImageElement"))return{width:t.naturalWidth,height:t.naturalHeight};if(e("HTMLVideoElement"))return{width:t.videoWidth,height:t.videoHeight};if(e("SVGImageElement"))throw new TypeError("SVGImageElement isn't yet supported as source image.","UnsupportedError");return e("HTMLCanvasElement")||e("ImageBitmap")?t:void 0}}(e);r<0&&(t+=r,r=Math.abs(r));n<0&&(a+=n,n=Math.abs(n));h<0&&(i+=h,h=Math.abs(h));m<0&&(o+=m,m=Math.abs(m));var g=Math.max(t,0),u=Math.min(t+r,s),s=Math.max(a,0),d=Math.min(a+n,d),r=h/r,n=m/n;return[e,g,s,u-g,d-s,t<0?i-t*r:i,a<0?o-a*n:o,(u-g)*r,(d-s)*n]}(...arguments);return r=n,[3,4,7,8].some(e=>!r[e])?void 0:i.apply(this,n)}:console.error("This script requires a basic implementation of drawImage")}})();
</script>
<div id="viewport">
<div class="sticker">
<!-- <canvas> isn't a void element, it must have a closing tag -->
<!-- We place it in a "sticky" element, outside of the one that gets transformed -->
<canvas id="cvs"></canvas>
</div>
<div id="wrapper">
<div id="placeholder"><!--
We'll use it as an easy way to measure what part of the canvas we should draw
based on the current scroll position.
--></div>
<div>
</div>
You can inspect the <canvas> element and see it's actually only as big as the viewport and not some 81920x81920px.

2D Infinitely looping Array of elements

The Goal :
The idea is to create an element grid (image gallery for exemple) that would infinitely loop on itself scrolling on two axes.
There should be no holes nor too much randomness (avoid having the same element randomly falling aside from itself). And this no matter how many element there is in the first place (it seems easy to infinite loop through a grid of 16 (4*4) elements, not that much over 17 (17*1). (My guess is that any prime number of elements is by definition a pain to make a grid of).
So I actually found a wonderful working exemple :
http://www.benstockley.com/
It's actually really close (probably better) than what I was imagining. Now it's using canvas and i tried looking at the javascript and it's a 30000 minified lines long script so I really can't read any core logic behind it.
Math side / Problem solving :
This is the logic and theory behind the problem, the math involved and the mindset.
How the program should process the list of elements so we have no holes, infinite grid, best repartion of the elements over all the axes.
My guess is that it somehow has to be procedural. I'm not sure if we should create grids or loop through the list on every axes (kind of like sudoku ? i don't know);
Pratical side / UI / UX :
Any advice on the technologies involved, pieces of code. I'm guessing it classic DOM is out of the way and that somehow canvas or 2D webgl will be mandatory. But I would love to hear any advice on this side.
Besides all the elements grid processing. The UI and UX involved in exploring a 2D infinite or vast layout in DOM or renderer is somehow not classical. The best technologies or advice on doing this are welcome.
Exemples :
I would welcome any working exemple that somewhat share an aspect of this problem.
I've got a fiddle that's set up to arrange your 2d grid.
It functions by using horizontal and vertical "step sizes". So, moving one step right in the grid advances the horizontal step size in the list. Moving one step down advances the vertical step size in the list (and they accumulate).
We allow the advances in the list to loop back to zero when the end is reached.
It likely makes sense to use a horizontal step size of 1 (so a row of your grid will maintain your list order). For the vertical step size, you want an integer that shares no common divisors with the list length. Though it's no guarantee, I used the (rounded) square root of the list length as something that will work in lots of cases.
I'll reproduce the fiddle here:
var list = ['red','green','blue','cyan','orange','yellow','pink'];
var hstep = 1;
var vstep = Math.ceil(Math.sqrt(list.length));
function getListItem(x,y) {
var index = x * hstep + y * vstep;
return list[index % list.length];
}
var elementSize = 30;
var gutterSize = 10;
function getOffset(x,y) {
return [10 + (elementSize + gutterSize) * x, 10 + (elementSize + gutterSize) * y];
}
var frame = $('.frame');
function drawElement(x,y) {
var listItem = getListItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': listItem
});
frame.append(element);
}
function drawElements() {
var x = 0, y = 0;
while (10 + (elementSize + gutterSize) * x < frame.width()) {
while (10 + (elementSize + gutterSize) * y < frame.height()) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
drawElements();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 300px;
position: relative;
overflow: hidden;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
.buttons {
position: absolute;
top: 0px;
width: 100%;
}
.buttons button {
position: absolute;
width: 30px;
height: 30px;
padding: 5px;
}
button.up {top: 0px; left: 46%;}
button.down {top: 355px; left: 46%;}
button.left {top: 160px; left: 15px;}
button.right {top: 160px; right: 15px;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
</div>
<div class="buttons">
<button class="up">↑</button>
<button class="down">↓</button>
<button class="left">←</button>
<button class="right">→</button>
</div>
You can see I've left some simple buttons to implement movement, but they are not functional yet. If you wanted to continue implementation along the lines of what I've done here, you could render your elements to a certain range beyond the visible frame, then implement some sort of animated repositioning. The renderElements function here only renders what is visible, so you can use something like that and not get stuck in rendering infinite elements, even though there's no theoretical limit to how far you can "scroll".
#arbuthnott I edited your code to implement the exploration via decrementing relativeX and relativeY variables. Also I inserted an "origin" div (1x1 px, overflow visible). This DOM element will represent the X and Y origin. I'm not sure it's essential but it's really convenient.
Now my function currently remove all elements and reinsert all elements on each update (every 500ms for now).
The idear would be to find a way to compare which elements I need versus which one already exists.
Maybe storing existing elements into an array, and compare the array with the "query" array. Than see just the elements that are missing.
This is the idear, not sure about the implementation (I suck at handling arrays).
https://jsfiddle.net/bnv6mumd/64/
var sources = ['red','green','blue','cyan','orange','yellow','pink','purple'];
var frame = $('.frame'),
origin = $('.origin');
var fWidth = 600,
fHeight = 300,
srcTotal = sources.length,
srcSquare = Math.ceil(Math.sqrt(srcTotal)),
rX = 0,
rY = 0;
var gridSize = 30,
gutterSize = 5,
elementSize = gridSize - gutterSize;
function getSourceItem(x,y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x,y) {
return [gridSize * x,gridSize * y];
}
function drawElement(x,y) {
var sourceItem = getSourceItem(x,y);
var offsets = getOffset(x,y);
var element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': sourceItem,
});
origin.append(element);
}
function init() {
var x = 0, y = 0;
while ( gridSize * x < fWidth) {
while ( gridSize * y < fHeight) {
drawElement(x,y);
y++;
}
y = 0;
x++;
}
}
function updateElements() {
origin.empty();
var x = -Math.trunc(rX / gridSize) -1, y = - Math.trunc(rY / gridSize) -1;
while ( gridSize * x + rX < fWidth) {
while ( gridSize * y + rY < fHeight) {
drawElement(x,y);
y++;
}
y = -Math.ceil(rY / gridSize);
x++;
}
}
function animate() {
rX -= 5;
rY -= 5;
origin.css({left: rX, top: rY})
updateElements();
console.log("relative X : " + rX + " | relative Y : " + rY);
}
setInterval(animate, 500)
init();
.frame {
border: 2px solid black;
margin: 40px auto;
height: 300px;
width: 600px;
position: relative;
overflow: hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 25px;
height: 25px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame">
<div class="origin" style="top:0;left:0;"></div>
</div>
This is my final snippet version (i will start to work on real implementation specific to my case now).
I think I optimized in a decent way DOM operations, code structure etc (I am very well open to suggestions though).
I now only update the elements that needs to be updated (click near the frame to show overflow)
https://jsfiddle.net/bnv6mumd/81/
var sources = ['red', 'green', 'blue', 'cyan', 'orange', 'yellow', 'pink', 'purple'];
var frame = $('.frame'),
origin = $('.origin');
var srcTotal = sources.length,
srcSquare = Math.round(Math.sqrt(srcTotal)),
fWidth = 200,
fHeight = 200,
cellSize = 50,
gutterSize = 20,
gridSize = [Math.floor(fWidth / cellSize) + 1, Math.floor(fHeight / cellSize) + 1],
aX = 0, // Absolute/Applied Coordinates
aY = 0,
rX = 0, // Relative/frame Coordinates
rY = 0;
function getSrcItem(x, y) {
var index = x + y * srcSquare;
return sources[Math.abs(index) % srcTotal];
}
function getOffset(x, y) {
return [cellSize * x, cellSize * y];
}
function getY() {
return Math.floor(-rY / cellSize);
}
function getX() {
return Math.floor(-rX / cellSize);
}
function drawElement(x, y) {
var srcItem = getSrcItem(x, y),
offsets = getOffset(x, y),
element = $('<div></div>').addClass('element').css({
left: offsets[0] + 'px',
top: offsets[1] + 'px',
'background-color': srcItem,
}).attr({
"X": x,
"Y": y
});
origin.append(element);
}
function drawCol(x, y) {
var maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawElement(x - 1, y - 1);
y++;
}
}
function drawLign(x, y) {
var maxX = x + gridSize[0];
while (x <= maxX + 1) {
drawElement(x - 1, y - 1);
x++;
}
}
function drawGrid() {
origin.empty();
var x = getX(),
y = getY(),
maxX = x + gridSize[0],
maxY = y + gridSize[1];
while (y <= maxY + 1) {
drawLign(x, y);
x = getX();
y++;
}
}
function updateX(x, y, diffX, diffY) {
if (Math.sign(diffX) == -1) {
drawCol(aX - 1, y);
$('[x=' + (aX + gridSize[0]) + ']').remove();
aX--;
} else if (Math.sign(diffY) == 1) {
drawCol(aX + gridSize[0] + 2, y);
$('[x=' + (aX - 1) + ']').remove();
aX++;
}
}
function updateY(x, y, diffX, diffY) {
if (Math.sign(diffY) == -1) {
drawLign(x, aY - 1);
$('[y=' + (aY + gridSize[0]) + ']').remove();
aY--;
} else if (Math.sign(diffY) == 1) {
drawLign(x, aY + gridSize[0] + 2);
$('[y=' + (aY - 1) + ']').remove();
aY++;
}
}
function animate() {
rX += 1;
rY += 1;
origin.css({
left: rX,
top: rY
});
var x = getX(),
y = getY(),
diffX = x - aX,
diffY = y - aY;
if (diffX) {
updateX(x, y, diffX, diffY)
};
if (diffY) {
updateY(x, y, diffX, diffY)
};
requestAnimationFrame(animate);
}
$('body').click(function() {
$(frame).toggleClass("overflow");
})
drawGrid();
animate();
.frame {
border: 2px solid black;
margin: 100px auto;
height: 200px;
width: 200px;
position: relative;
}
.overflow{
overflow:hidden;
}
.origin {
height: 1px;
width: 1px;
position: absolute;
overflow: visible;
}
.frame .element {
position: absolute;
width: 30px;
height: 30px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="frame overflow">
<div class="origin" style="top:0;left:0;"></div>
</div>

Some bugs in code using Canvas

I have a scratch card simulator.
The user should be able to click and drag in order to reveal the text underneath.
I have 2 bugs with this implementaion:
a) Sometimes the scratch card clears itself as soon as the cursor enters the canvas from left or right. It should only clear itself when most of the card has been scratched off. Currently, it only works if the user moves their cursor in from the top border, going downwards.
b) Sometimes the scratch card will not work at all, or the scratching will be offset from the cursor, but only when the browser window is smaller than the document size (e.g., browser window is 300px wide but the body has a min-width of 900px or something and the user had to scroll the card into view).
(function () {
"use strict";
var container = document.getElementById('cbox-canvas'),
arrow = document.getElementById('cbox-arrow'),
textOne = document.getElementById('cbox-text-1'),
textTwo = document.getElementById('cbox-text-2'),
boxOne = document.getElementById('cbox-box-1'),
boxTwo = document.getElementById('cbox-box-2'),
cnv = container.getElementsByTagName('canvas'),
imageCover;
function createCanvas(parent, width, height) {
var canvas = {};
canvas.node = document.createElement('canvas');
canvas.context = canvas.node.getContext('2d');
canvas.node.width = width || 100;
canvas.node.height = height || 100;
parent.appendChild(canvas.node);
return canvas;
}
function init(container, width, height, fillColor) {
var canvas = createCanvas(container, width, height),
ctx = canvas.context;
// define a custom fillCircle method
ctx.fillCircle = function (x, y, radius, fillColor) {
//this.fillStyle = fillColor;
this.shadowBlur = 15;
this.shadowOffsetX = 0;
this.shadowOffsetY = 0;
this.shadowColor = fillColor;
this.beginPath();
this.moveTo(x, y);
this.arc(x, y, radius, 0, Math.PI * 2, false);
this.fill();
this.stroke();
};
ctx.clearTo = function (fillColor) {
var imageObj = new Image();
imageObj.onload = function () {
ctx.drawImage(imageObj, 0, 0);
};
imageObj.src = fillColor;
};
ctx.clearTo(fillColor || "#ddd");
// bind mouse events
canvas.node.onmousemove = function (e) {
var canvasRect = container.getBoundingClientRect(),
x = e.pageX - canvasRect.left,
y = e.pageY - canvasRect.top,
radius = 30,
calc = 0;
fillColor = '#ff0000';
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
calc += x;
if (calc > 330 || calc < 6) {
container.removeChild(cnv[0]);
arrow.className += " slide-it";
textOne.className += " reveal-it";
textTwo.className += " fade-in";
boxOne.className += " fade-in-two";
boxTwo.className += " fade-in-one";
}
};
container.onmousemove = function (e) {
var canvasRect = container.getBoundingClientRect(),
mouseX = e.pageX || e.clientX,
mouseY = e.pageY || e.clientY,
relMouseX = mouseX - canvasRect.left,
relMouseY = mouseY - canvasRect.top,
leftLimit = 37,
topLimit = 37,
rightLimit = 25,
bottomLimit = 44,
x = e.pageX - canvasRect.left,
y = e.pageY - canvasRect.top,
radius = 25;
fillColor = '#ff0000';
if (relMouseX < leftLimit) {
relMouseX = leftLimit;
}
if (relMouseY < topLimit) {
relMouseY = topLimit;
}
if (relMouseX > width - rightLimit) {
relMouseX = width - rightLimit;
}
if (relMouseY > height - bottomLimit) {
relMouseY = height - bottomLimit;
}
if (!canvas.isDrawing) {
return;
}
ctx.globalCompositeOperation = 'destination-out';
ctx.fillCircle(x, y, radius, fillColor);
};
}
imageCover = "images/scratch.png";
init(container, 369, 371, imageCover);
}());
https://jsfiddle.net/p05kg0vq/
Problems
There are several issues here:
You are loading the clear image for each time which happens asynchronously and it may not appear in time.
The same method, clearTo(), also takes a fill-style color, but tries to load it as image
You are listening to mouse move events two places which is not needed
For improvement: You're listening to mouse move events on the canvas. This is not wrong, but it will be more fluid using the window object
You're using pageX/Y to calculate mouse position. These are relative to page not client. getBoundingClientRect() is relative to client.
Not sure how you intend to calculate coverage
There are additional room for refactoring.
Solutions
Load image once globally and use the object as argument instead of the URL.
Differentiate between an image and color string. To do this check if argument is a string, if so, set fillStyle and use fillRect() to clear. If not use drawImage().
Remove the event from the container. It's not needed and will conflict with the second listener.
Use window.onmousemove instead (not required, but a better option in this case as it will move the cursor completely outside canvas - optionally use a parent node that is wider - it's up to you...).
Calculate using clientX and clientY instead and always.
Extract the ImageData and count pixels with no alpha data (=0). Then divide this count on total number of pixels to get percentage of coverage. This is fast (I'll show how below).
Left to OP to improve :)
So, lets modify the structure a bit. This is not optimal, but meant to get you started. Load the image once and globally (or within the parent scope so the object is accessible).
// preload image once
var imageCover = "//i.imgur.com/b4m1M1n.png"; // needed cors for demo
var imageObj = new Image();
imageObj.onload = go;
imageObj.crossOrigin = ""; // for demo, for getImageData to work
imageObj.src = imageCover;
function go() {
/* ... inner code not shown ... */
init(container, 369, 371, imageObj);
};
Then rewrite clearTo() to accept both image and fill style. Notice this may break browser optimizations as there are two different types involved, but in this case it likely doesn't matter:
ctx.clearTo = function(fillColor) {
if (typeof fillColor === "string") { // is a string?
ctx.fillStyle = fillColor; // set as fill style
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
else { // assumes an image if not string
ctx.drawImage(fillColor, 0, 0);
}
};
ctx.clearTo(fillColor || "#ddd");
Then move onmousemove to window object and use clientX/clientY:
window.onmousemove = function(e) {
var canvasRect = container.getBoundingClientRect(),
x = e.clientX - canvasRect.left, // use clientX/Y (pageXY is unofficial)
y = e.clientY - canvasRect.top,
/* ... */
Within the same code block provide a function calculate real-time coverage of canvas:
// calc converage and clean if < 20%
if (calcCover(ctx) < 0.2) {
// end, reveal, etc.
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
console.log("DONE");
};
The function used here does:
function calcCover(ctx) {
var w = ctx.canvas.width, // just to cache width/height
h = ctx.canvas.height,
// convert Uint8ClampedArray to Uint32Array, no memory loss
// but faster
data32 = new Uint32Array(ctx.getImageData(0,0,w,h).data.buffer),
count = w * h; // total number of pixels
// iterate, check for alpha-channel (0xAABBGGRR, little-endian format)
// for the Uint32Array data.
for(var i = 0; i < data32.length; i++) if (!(data32[i] & 0xff000000)) count--;
// convert to a percentage (or rather normalize)
return count / (w*h);
};
Now we are good to go:
Modified fiddle
Looking at your code I could only suggest a full rewrite
;(function () {
"use strict";
// Generic functions and constants
const PI2 = Math.PI * 2;
function applyStyle (ctx, style) { Object.keys(style).forEach(key => ctx[key] = style[key] ) }
function ease (val, power) { return val < 0 ? 0 : val > 1 ? 1 : Math.pow(val, power) }
// General settings
const settings = {
width : 369,
height : 371,
coveragedMin : 0.2, // when to uncover all out of 1
coverColor : "#ddd", // colour to show on canvas while main image is loading. (not needed but to keep with you code)
mouseEvents : "mouseup,mousedown,mousemove".split(","), // list of mouse events to listen to
coverImage : loadImage("https://image.ibb.co/f8TNS5/scratch.png"), // the scratch image
container : document.getElementById('cbox-canvas'), // the container
drawStyle : { // the draw style of the revealing mouse moves. Note that this adds radius to the context but should not matter
radius : 20,
shadowBlur : 15,
shadowOffsetX : 0,
shadowOffsetY : 0,
shadowColor : "black",
fillStyle : "black",
globalCompositeOperation : "destination-out",
},
startAnim (){ // specific to this scratch reveal animations
document.getElementById("cbox-arrow").className = "cbox-arrow slide-it";
document.getElementById("cbox-text-1").className = "cbox-text-1 reveal-it";
document.getElementById("cbox-box-1").className = "cbox-box-1 fade-in-two";
document.getElementById("cbox-box-2").className = "cbox-box-2 fade-in-one";
document.getElementById("cbox-text-2").className = "cbox-text-2 fade-in";
},
coverageArray : (() => {const buf = new Uint8Array(64); buf.fill(1); return buf }) (), // array to is used to determine coverage
}
var update = true; // when true update canvas render
const mouse = { x : 0, y : 0, button : false}; // Mouse state
function mouseEvent (e) { // handles all mouse events
const canvasRect = settings.container.getBoundingClientRect();
mouse.x = e.pageX - canvasRect.left - scrollX;
mouse.y = e.pageY - canvasRect.top - scrollY;
if (e.type === "mousedown") { mouse.button = true }
else if (e.type === "mouseup") { mouse.button = false }
update = true; // flags that there needs to be a re render
}
function fillCircle (ctx, x, y, style) { // Draws a circle on context ctx, at location x,y using style
applyStyle(ctx, style);
ctx.beginPath();
ctx.arc(x, y, style.radius, 0, Math.PI * 2);
ctx.fill();
}
function setCoverage (array,x,y){ // Clears the coverage array, coordinates x,y are normalised 0-1
var i = array.length - 1; // and returns coverage as a value 0 no coverage to 1 full cover
const size = Math.sqrt(array.length) | 0;
array[(x * size) | 0 + ((y * size) | 0) * size] = 0;
var count = 0;
while(i-- > 0){ count += array[i] };
return count / array.length;
}
function loadImage (url) { // Loads an image and sets a property indicating if its has been rendered
const image = new Image();
image.src = url;
image.rendered = false;
return image;
}
function createCanvas (width, height) { // Creates a canvas of size width and height, set property ctx to the 2D context
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.ctx = canvas.getContext("2d");
return canvas;
}
(function (settings) { // Start the app
const canvas = createCanvas(settings.width, settings.height);
settings.container.appendChild(canvas);
const ctx = canvas.ctx;
settings.mouseEvents.forEach(eventName => addEventListener(eventName, mouseEvent)); // start the mouse
var reveal = false; // when true reveal the prize (yep suckers) ???
var fade = 1; // fades out the canvas
ctx.fillStyle = settings.coverColor; // cover while waiting for image to load
ctx.fillRect(0, 0, canvas.width, canvas.height); // image will not yet have loaded so cover image
(function mainLoop () { // main animation loop will play unt ill canvas faded out
if (settings.coverImage.complete && !settings.coverImage.rendered) { // wait till image has loaded
ctx.globalCompositeOperation = "source-over";
ctx.drawImage(settings.coverImage, 0, 0, settings.width, settings.height);
settings.coverImage.rendered = true;
const swipeEl = document.getElementById("swipe-area");
swipeEl.className = "swipe-area loaded";
swipeEl.title = "Use your mouse to reveal your PRIZE :P";
}
if (update) { // only if needed render canvas
if (settings.coverImage.rendered) {
mouse.button && fillCircle(ctx, mouse.x, mouse.y, settings.drawStyle);
setCoverage(settings.coverageArray, mouse.x / settings.width, mouse.y / settings.height) < settings.coveragedMin && (reveal = true);
update = false;
}
if (reveal) {
fade -= 0.05;
canvas.style.opacity = ease(fade,2);
update = true; // need continuous update for animation
}
}
if (reveal && fade <= 0) { // scratching all done remove canvas, mouse events and start any animations. Do not call requestAnimationFrame as all done.
const swipeEl = document.getElementById("swipe-area");
swipeEl.style.cursor = "pointer";
swipeEl.title = "Click here to collect your $$$$";
settings.container.removeChild(canvas);
settings.mouseEvents.forEach(eventName => removeEventListener(eventName, mouseEvent));
settings.startAnim();
// All done. All objects should now have no references (important to remove mouse and requestAnimation frame) and any other functions
// that can hold a closure
} else {
requestAnimationFrame(mainLoop);
}
} () );
} (settings) );
} () );
/*SWIPE*/
.swipe-area {
position: absolute;
width: 369px;
height: 371px;
left: 10px;
top: 10px;
z-index: 15;
background-size: 100%;
}
.preload {
cursor : wait;
background: url('https://image.ibb.co/f8TNS5/scratch.png') no-repeat;
background-size: 100%;
}
.loaded {
cursor : url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAbCAYAAABm409WAAABkklEQVRIibXWPWtUQRjF8d/GbDRFomghZFNorREMiIUgpEgRSFJFuyAWNoovTQIqdvZp8lXCBb+ApZ9AApb2lmFS3Jns3XVm9ya7+8DDwMA9/3vOM/eFfC0W9qdSodHTFz46FkIQfv4S8GdaoCQY6AMaoIkgoapqodQJkgCTQgbE/4ecTOwkC+hDTi76qpAiYBAysHc6M0gIM3MxOPjLQkYCcpBLA4aPagHyNwGqasYurgRo6SIH6UzVRVX1OwKWcAMLmBsFOGvrYgiwhvtYxU3MTxRVxsVvbGEDD3Ab10qAzriomoCj4wsX+9jDM/RiXNmax+NxTsRvRwNwgI94GSNbUhj+Qszx7SgnCdCYwzd8xms8wS2FgSfAOr4348jF1BD/isMY1XrUyAK60d4aXuCH/NFMfYgvcf3UJqK56KKnHtge3uDfkPAHvI/rO7yKNzR2yKkWsYKHeI5NbGMHu3HdiXubeIpHUbz1b0+K62688F6he7iD5TZ3Plwd9dHt4nqhu+oHq/U7KQfpqOeT66LwOUobOkEQCJp1AAAAAElFTkSuQmCC') 9 20, pointer;
background: url('https://image.ibb.co/j4j7uk/sc_bg.png') no-repeat;
background-size: 100%;
}
.anim-container {
position: absolute;
width: 360px;
height: 366px;
right: 5px;
top: 5px;
z-index: -1;
background-size: 100%;
overflow: hidden;
}
.cbox-arrow {
position: absolute;
left: 56px;
top: 0;
z-index: -1;
width: 260px;
height: 264px;
background: url('https://image.ibb.co/fXKwn5/arrow.png') no-repeat;
background-size: 100%;
-webkit-animation: 10s slide;
}
.cbox-text-1 {
position: absolute;
left: 72px;
top: 100px;
z-index: -1;
width: 230px;
height: 65px;
background: url('https://image.ibb.co/d6YYZk/test1.png') no-repeat;
background-size: 100%;
opacity: 1;
}
.cbox-text-2 {
position: absolute;
left: 72px;
top: 100px;
z-index: -1;
width: 230px;
height: 65px;
background: url('https://image.ibb.co/bCaQfQ/test2.png') no-repeat;
background-size: 100%;
opacity: 0;
}
.cbox-box-1 {
position: absolute;
left: 55px;
top: 167px;
z-index: -1;
width: 257px;
height: 65px;
background: url('https://image.ibb.co/fG7hS5/box1.png') no-repeat;
background-size: 100%;
opacity: 0;
}
.cbox-box-2 {
position: absolute;
left: 135px;
top: 124px;
z-index: -1;
width: 99px;
height: 127px;
background: url('https://image.ibb.co/dOSSuk/box2.png') no-repeat;
background-size: 100%;
opacity: 0;
}
.hidden { display: none;}
/* unknowns */
.newslisting, #sidebar1Bottom { background: #ffffff !important; }
.tmx header { height: 269px;}
/*Animations, you can add agent prescripts, though we should never have to do that */
.fade-in { animation: 1.5s 2.5s fade;}
.fade-in-one { animation: 2.5s 5s fade;}
.fade-in-two { animation: 2.5s 7.5s fade-alt forwards;}
.fade-in-three { animation: 5s 15s fade;}
.reveal-it { animation: 2.5s reveal forwards;}
.slide-it { animation: 5s slide-in forwards;}
#keyframes fade-alt {0% { opacity: 0; } 10% {opacity : 1;} 100% { opacity: 1;} }
#keyframes fade { 0% { opacity: 0; } 10% {opacity: 1;} 90% {opacity: 1;} 100% {opacity: 0;} }
#keyframes reveal {0% { opacity: 1;} 80% { opacity: 1;} 100% { opacity: 0;} }
#keyframes slide-in {0% { top: 5px; } 80% { top: 5px; } 100% { top: -150px;} }
<div class="swipe-area preload" id="swipe-area" title="Just a moment as we asses your gullibility!">
<div id="cbox-canvas">
<div class="anim-container">
<div id="cbox-arrow" class="hidden"></div>
<div id="cbox-text-1" class="hidden"></div>
<div id="cbox-box-1" class="hidden"></div>
<div id="cbox-box-2" class="hidden"></div>
<div id="cbox-text-2" class="hidden"></div>
</div>
</div>
</div>

Categories