Sprite PNG Javascript animation not working - javascript

I'm trying to create a Sprite animation using the following image:
To do so I am using it as a background and am trying to manipulate the background's position when animating. Somehow I can't get it working though - it shows the last frame from the very beginning.
Image: https://i.imgur.com/06vjVVj.png - 30800x1398 and 27 frames
Here's a codepen: https://codepen.io/magiix/pen/MWewdYo
#skull {
position: absolute;
border: 1px solid red;
width: 1140px;
height: 1398px;
background: url("https://i.imgur.com/06vjVVj.png") 1140px 0;
}
const animateSkull = () => {
const interval = 50;
let pos = 30800 / 27;
tID = setInterval(() => {
document.getElementById("skull").style.backgroundPosition = `-${pos}px 0`;
if (pos < 30800) {
pos = pos + 1140;
}
}, interval);
};

If you check (with a console for example), you'll see that your animateSkull function is never called, because your addEventListener does not work. Change it to the following so it will be called (but your animateSkull function has another bug (or maybe your css I didn't checked) so it's not fully working after that but you should be able to fix that easily):
document.addEventListener('DOMContentLoaded', () => {
animateSkull();
});

This should do the work, but the frames in your sprite don't have the same width. So the animation looks buggy. (that is one huge image just for the animation)
const animateSkull = () => {
const interval = 1000;
let pos = -1140;
tID = setInterval(() => {
if (pos > -30800) {
pos -= 1140;
}
document.getElementById("skull").style.backgroundPosition = `${pos}px 0`;
}, interval);
};
animateSkull();
#skull {
position: absolute;
border: 1px solid red;
width: 1140px;
height: 1398px;
background-image: url("https://i.imgur.com/06vjVVj.png");
background-position: -1140px 0;
background-size: cover;
}
</style>
<p id="skull"></p>

Related

Javascript animation trigger only when reaching a certain part of webpage

I want a counter animation which is triggered only when webpage reaches that certain part. For example, the js file would be this. I want the count to start only when the page reaches that certain section.
const counters = document.querySelectorAll('.counter');
const speed = 200; // The lower the slower
counters.forEach(counter => {
const updateCount = () => {
const target = +counter.getAttribute('data-target');
const count = +counter.innerText;
// Lower inc to slow and higher to slow
const inc = target / speed;
// console.log(inc);
// console.log(count);
// Check if target is reached
if (count < target) {
// Add inc to count and output in counter
counter.innerText = count + inc;
// Call function every ms
setTimeout(updateCount, 1);
} else {
counter.innerText = target;
}
};
updateCount();
});
Yo can easily do it using Jquery
$(document).ready(function() {
$(window).scroll(function() {
if ($(document).scrollTop() > 50) {
$("div").css("background-color", "#111111");
} else {
$("div").css("background-color", "transparent");
}
});
});
div {
height: 120vh;
transition-duration: 0.5s;
}
<div>
Scroll to Change Background
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
You can use Intersection Observer to do that
const $observeSection = document.querySelector('#second');
const options = {
root: null,
rootMargin: '0px',
threshold: 0.5
};
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.intersectionRatio > options.threshold) {
$observeSection.classList.add('yellow');
} else {
$observeSection.classList.remove('yellow');
}
});
}, options);
observer.observe($observeSection);
main {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
main section {
width: 100vw;
height: 100vh;
}
#first {
background: red;
}
#second {
background: blue;
}
#third {
background: green;
}
.yellow {
background: yellow!important;
}
<main>
<section id="first"></section>
<section id="second"></section>
<section id="third"></section>
</main>
In this example, I observe the second section, and when the scroll come to the middle of the section (threshold: 0.5), I add a class to change the color of this section.
Be careful if you need to handle legacies browsers as you can see here :
https://caniuse.com/#feat=intersectionobserver
You don't need jquery to achieve this.
Here is a VanillaJS solution:
window.onscroll = (e) => {
e.stopPropagation();
window.pageYOffset > 50 && console.log("do smh");
}

How can I use requestAnimationFrame along with setInterval?

See the following snippet of code.
It creates a loader on click of a button. But the animation is not smooth.
I have recently read about requestAnimationFrame function which can do this job. But how can I use it to replace setInterval altogether since there is no way to specify time in requestAnimationFrame function.
Can it be used in conjunction with setInterval ?
let idx = 1;
const timetoEnd = 5000;
function ProgressBar(width){
this.width = width || 0;
this.id = `pBar-${idx++}`;
this.create = () => {
let pBar = document.createElement('div');
pBar.id = this.id;
pBar.className = `p-bar`;
pBar.innerHTML = `<div class="loader"></div>`;
return pBar;
};
this.animator = () => {
let element = document.querySelector(`#${(this.id)} div`);
if(this.width < 100){
this.width++;
element.style.width = `${this.width}%`;
} else {
clearInterval(this.interval);
}
};
this.animate = () => {
this.interval = setInterval(this.animator, timetoEnd/100);
}
}
function addLoader (){
let bar1 = new ProgressBar(40);
let container = document.querySelector("#container");
container.appendChild(bar1.create());
bar1.animate();
}
.p-bar{
width: 400px;
height: 20px;
background: 1px solid #ccc;
margin: 10px;
overflow:hidden;
border-radius: 4px;
}
.p-bar .loader{
width: 0;
background: #1565C0;
height: 100%;
}
<input type="button" value="Add loader" onclick="addLoader()" />
<div id="container"></div>
You are right, requestAnimationFrame is the recommended way to avoid UI jam when doing animation.
You can remember the absolute starting time at start instead of trying to do this at each frame. Then it's just a matter of computing a width based on the delta time between start and current time.
Also, document.querySelector is considered a relatively "heavy" operation so I added this.element to avoid doing it at each frame.
Here is how to new width is computed: ((100 - this.startWidth) / timetoEnd) * deltaT + this.startWidth
100 - this.startWidth is the total amount of width we have to animate
(100 - this.startWidth) / timetoEnd is how much width each second must add to (1)
((100 - this.startWidth) / timetoEnd) * deltaT is how much width we have to add to (1)
We just have to shift the whole thing this.startWidth px to have the frame's width
Also notice that some of this computation is constant and do not have to be computed on each frame, which I left as an exercise :)
Here is your slightly adapted code:
let idx = 1;
const timetoEnd = 5000;
function ProgressBar(startWidth){
this.startWidth = startWidth || 0;
this.id = `pBar-${idx++}`;
this.create = () => {
let pBar = document.createElement('div');
pBar.id = this.id;
pBar.className = `p-bar`;
pBar.innerHTML = `<div class="loader"></div>`;
return pBar;
};
this.animator = () => {
const deltaT = Math.min(new Date().getTime() - this.start, timetoEnd);
if(deltaT < timetoEnd){
const width = ((100 - this.startWidth) / timetoEnd) * deltaT + this.startWidth;
this.element.style.width = `${width}%`;
requestAnimationFrame(this.animator.bind(this))
}
};
this.animate = () => {
this.element = document.querySelector(`#${(this.id)} div`);
this.start = new Date().getTime();
this.animator();
}
}
function addLoader (){
let bar1 = new ProgressBar(40);
let container = document.querySelector("#container");
container.appendChild(bar1.create());
bar1.animate();
}
.p-bar{
width: 400px;
height: 20px;
background: 1px solid #ccc;
margin: 10px;
overflow:hidden;
border-radius: 4px;
}
.p-bar .loader{
width: 0;
background: #1565C0;
height: 100%;
}
<input type="button" value="Add loader" onclick="addLoader()" />
<div id="container"></div>

Link Background Color to Scroll Position

I want to link the background color of the body element to the scroll position such that when the page is scrolled all the way to the top its color 1, but then but then when its scrolled past screen.height, its a completely different color, but I want it to be interpolated such that when it is half-way scrolled, the color is only half-way transitioned. So far, I have it linked to
$(window).scrollTop() > screen.height
and
$(window).scrollTop() < screen.height
to add and remove a class that changes background-color but I want it to be dependent on scroll position not just to trigger the event, but rather smoothly animate it so fast scrolling transitions quickly, slow scrolling transitions it slowly.
One of possible solutions is to bind a rgb color to current height, count the step and set new rgb color depending on current position of scrolling. Here I've created the simplest case - black and white transition:
const step = 255 / $('#wrapper').height();
const multiplier = Math.round(
$('#wrapper').height() /
$('#wrapper').parent().height()
);
$('body').scroll(() => {
const currentStyle = $('body').css('backgroundColor');
const rgbValues = currentStyle.substring(
currentStyle.lastIndexOf("(") + 1,
currentStyle.lastIndexOf(")")
);
const scrolled = $('body').scrollTop();
const newValue = step * scrolled * multiplier;
$('#wrapper').css('background-color', `rgb(${newValue}, ${newValue}, ${newValue})`);
});
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
background-color: rgb(0, 0, 0);
}
#wrapper {
height: 200%;
width: 100%;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<section id="wrapper"></section>
And here is another one example with transition from yellow to blue:
const step = 255 / $('#wrapper').height();
const multiplier = Math.round(
$('#wrapper').height() /
$('#wrapper').parent().height()
);
$('body').scroll(() => {
const currentStyle = $('body').css('backgroundColor');
const rgbValues = currentStyle.substring(
currentStyle.lastIndexOf("(") + 1,
currentStyle.lastIndexOf(")")
);
const scrolled = $('body').scrollTop();
const newValue = step * scrolled * multiplier;
$('#wrapper').css('background-color', `rgb(${255 - newValue}, ${255 - newValue}, ${newValue})`);
});
html,
body {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
overflow-x: hidden;
background-color: rgb(255, 255, 0);
}
#wrapper {
height: 200%;
width: 100%;
overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"></script>
<section id="wrapper"></section>
var randomHex = function () {
return (parseInt(Math.random()*16)).toString(16) || '0';
};
var randomColor = function () {
return '#'+randomHex()+randomHex()+randomHex();
};
var randomGradient = function () {
$('.longContent').css('background', 'linear-gradient(0.5turn, #222, '+randomColor()+','+randomColor()+')');
};
$(window).on('load', randomGradient);
body {
margin: 0;
}
.longContent {
height: 400vh;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tween.js/17.2.0/Tween.min.js"></script>
<div class="longContent"></div>
A much, much easier way to accomplish what you're looking to do is by simply using a gradient as the background.
There is absolutely zero need for any JS here, which will only slow down the page.
body {
height: 600vh;
background: linear-gradient(#2E0854, #EE3B3B)
}
Is there a particular reason you want to do this with JS?

Tile based javascript game - laggy scroll

I am creating a tile based game in javascript. I am absolutely beginner.
The plan is to learn javascript while I trying to make this game. I am having serious lagging issues when I am trying to scroll the "game"
For a live preview you can see here whats bad:
http://iwansfactory.com/tycoon/index.html
My javascript generates them and they HTML part looks like this:
<div class="tiletype100" id="x0y0" style="left: 2151px; top: -540px;"></div>
The css:
.tiletype2 {
z-index: 0;
position: absolute;
width: 600px;
height: 800px;
background-image: url("http://iwansfactory.com/tycoon/road2.png");
background-repeat: no-repeat;
}
The javascript scroll function is this:
var right = document.documentElement.scrollLeft;
var bottom = document.documentElement.scrollTop;
var rightscrollvalue = 0;
var bottomscrollvalue = 0;
function rightenter() {
rightscrollvalue = 40;
scrollright();
}
function leftenter() {
rightscrollvalue = -40;
scrollright();
}
function rightout() {
rightscrollvalue = 0;
}
function scrollright() {
if (rightscrollvalue != 0) {
right = right + rightscrollvalue;
console.log(right);
window.scrollTo(right, bottom);
setTimeout(function() {
scrollright();
}, 50);
}
}
function bottomenter() {
bottomscrollvalue = 40;
scrollbottom();
}
function topenter() {
bottomscrollvalue = -40;
scrollbottom();
}
function bottomout() {
bottomscrollvalue = 0;
}
function scrollbottom() {
if (bottomscrollvalue != 0) {
bottom = bottom + bottomscrollvalue;
console.log(bottom);
window.scrollTo(right, bottom);
setTimeout(function() {
scrollbottom();
}, 50);
}
}
Your design uses large overlapping tiles that are mostly transparent. This requires a lot of CPU power for rendering, thus making the game laggy.
I propose you make your tiles smaller, just as large as they have to be (so there is a non-transparent pixel on every edge of the image) and use offsets for larger tiles so they get rendered at the right position.

Using two of the same javascripts

I've been trying to create a page with several before and after images (Using a slider to swap between the two).
However when I add the second piece of JavaScript code, it breaks the page. Even if I try to amend the (var) code to be unique from the previous script
In all honesty I don't quite understand what the JavaScript is doing which is why I'm probably unable to Google the solution. Any help would be appreciated, if you could try to explain in as much detail what I need to do and explain any specific terms that would help me develop further.
You can see all my code on the link (and below): http://codepen.io/sn0wm0nkey/pen/DakbA
var inkbox = document.getElementById("inked-painted");
var colorbox = document.getElementById("colored");
var fillerImage = document.getElementById("inked");
inkbox.addEventListener("mousemove",trackLocation,false);
inkbox.addEventListener("touchstart",trackLocation,false);
inkbox.addEventListener("touchmove",trackLocation,false);
function trackLocation(e)
{
var rect = inked.getBoundingClientRect();
var position = ((e.pageX - rect.left) / inked.offsetWidth)*100;
if (position <= 100) { colorbox.style.width = position+"%"; }
}
/* -----second JavaScript code---- */
var inkbox1 = document.getElementById("inked1-painted");
var colorbox1 = document.getElementById("colored1");
var fillerImage1 = document.getElementById("inked1");
inkbox1.addEventListener("mousemove",trackLocation,false);
inkbox1.addEventListener("touchstart",trackLocation,false);
inkbox1.addEventListener("touchmove",trackLocation,false);
function trackLocation(e1)
{
var rect1 = inked.getBoundingClientRect();
var position1 = ((e1.pageX - rect1.left) / inked1.offsetWidth)*100;
if (position1 <= 100) { colorbox1.style.width = position1+"%"; }
}
body { background: #113; }
div#inked-painted {
position: relative; font-size: 0;
-ms-touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
div#inked-painted img {
width: 100%; height: auto;
}
div#colored {
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/colored-panel.jpg);
position: absolute;
top: 0; left: 0; height: 100%;
width: 50%;
background-size: cover;
}
div#inked-painted:hover {
cursor: col-resize;
}
/*-------- second css sheet --------- */
div#inked1-painted {
position: relative; font-size: 0;
-ms-touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
div#inked1-painted img {
width: 100%; height: auto;
}
div#colored1 {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
background-size: cover;
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/colored-panel.jpg);
}
div#inked1-painted:hover {
cursor: col-resize;
}
<Div>
<div id="inked-painted">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/inked-panel.png" id="inked" alt>
<div id="colored"></div>
</div>
<p></p>
<div>
<div id="inked1-painted">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/inked-panel.png" id="inked1" alt>
<div id="colored1"></div>
</div>
Howard's solution works but can be improved even more to remove duplication.
Use classes instead of Ids
Find the elements inside the div that is receiving the mousemove instead of passing them in
Don't duplicate the CSS
function onMouseMove(e) {
var inked = this.getElementsByTagName("img")[0];
var colorbox = this.querySelector('.colored');
var rect = inked.getBoundingClientRect();
var position = ((e.pageX - rect.left) / inked.offsetWidth) * 100;
if (position <= 100) {
colorbox.style.width = position + "%";
}
}
function trackLocation(el) {
el.addEventListener("mousemove", onMouseMove, false);
el.addEventListener("touchstart", onMouseMove, false);
el.addEventListener("touchmove", onMouseMove, false);
}
var wrappers = document.querySelectorAll('.inked-painted');
for (var i = 0; i < wrappers.length; i++) {
trackLocation(wrappers[i]);
}
div.inked-painted {
position: relative;
font-size: 0;
-ms-touch-action: none;
-webkit-touch-callout: none;
-webkit-user-select: none;
}
div.inked-painted img {
width: 100%;
height: auto;
}
.colored {
background-image: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/colored-panel.jpg);
position: absolute;
top: 0;
left: 0;
height: 100%;
width: 50%;
background-size: cover;
}
div.inked-painted:hover {
cursor: col-resize;
}
<div class="inked-painted">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/inked-panel.png" />
<div class="colored"></div>
</div>
<p></p>
<div class="inked-painted">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/4273/inked-panel.png" />
<div class="colored"></div>
</div>
First, Java != JavaScript. They are two very different languages.
Second, your issue is that your second function is named the same as your first function. The second one essentially overwrites the first one, so the first doesn't exist any longer. Simply use a different name for your second function, and your code works just fine.
However, it would be better to find a way to reuse your first function, instead of having two almost identical functions.
Here is what you are doing with your JavaScript how it is currently written.
Declare and assign variables inkbox, colorbox, fillerImage
Add event handlers
Create a function in the global scope by the name of trackLocation
Declare and assign variables inkbox1, colorbox1, fillerImage1
Add event handlers
Overwrite the trackLocation function in the global scope
All of this is being done synchronously, just as I have it listed here. So, when an event fires on inkbox, it calls the new function that overwrote the original.
Another problem that I see (unless you omitted some code) is you have some variables that are not defined, which will cause a problem within your function.
function trackLocation (e) {
// inked is undefined
var rect = inked.getBoundingClientRect();
// inked is undefined
var position = ((e.pageX - rect.left) / inked.offsetWidth)*100;
if (position <= 100) { colorbox.style.width = position+"%"; }
}
You'll need to rewrite your function to accept local variables like this:
function trackLocation (e, inked, colorbox) {
var rect = inked.getBoundingClientRect();
var position = ((e.pageX - rect.left) / inked.offsetWidth)*100;
if (position <= 100) { colorbox.style.width = position+"%"; }
}
Now this one function can be reused in all of your event handlers, like this:
function trackLocation (e, inked, colorbox) {
var rect = inked.getBoundingClientRect();
var position = ((e.pageX - rect.left) / inked.offsetWidth)*100;
if (position <= 100) { colorbox.style.width = position+"%"; }
}
var inkbox = document.getElementById("inked-painted");
var colorbox = document.getElementById("colored");
var fillerImage = document.getElementById("inked");
inkbox.addEventListener("mousemove", function (e) { trackLocation(e, inkbox, colorbox); });
inkbox.addEventListener("touchstart", function (e) { trackLocation(e, inkbox, colorbox); });
inkbox.addEventListener("touchmove", function (e) { trackLocation(e, inkbox, colorbox); });
var inkbox1 = document.getElementById("inked1-painted");
var colorbox1 = document.getElementById("colored1");
var fillerImage1 = document.getElementById("inked1");
inkbox1.addEventListener("mousemove", function (e) { trackLocation(e, inkbox1, colorbox1); });
inkbox1.addEventListener("touchstart", function (e) { trackLocation(e, inkbox1, colorbox1); });
inkbox1.addEventListener("touchmove", function (e) { trackLocation(e, inkbox1, colorbox1); });

Categories