Creating dynamic gradient from a grayscale color - javascript

I'm trying to generate a gradient for grayscale colors with the help of color.js library. The code below grabs the CSS variable --color and generates different tones by using the devalueByAmount() method. The gradient is successfully generated, however if possible I need help to generate smoother gradients.
Currently, the colors at the starting and ending points of the gradient seems too obvious. I'd like to smudge them out for smoother transition. Any ideas how I could achieve this? Any help would be much appreciated!
const root = document.documentElement;
const Color = net.brehaut.Color;
const dropBy = 0.2;
let numOfTones = 500;
let color = Color(getComputedStyle(document.documentElement).getPropertyValue('--color'));
let scheme = [...Array(numOfTones).keys()].map((i, _, a) => {
let drop = ((dropBy / numOfTones) * (i+1)).toFixed(3);
let tone = (i != 0) ? color.devalueByAmount(drop) : color;
return tone;
});
let grad = [...Array(numOfTones * 2).keys()].map((i, _, a) => {
let numOfSteps = a.length - 2;
let breakPoint = (((100 / 2) / (numOfSteps)) * (i-1)).toFixed(6);
let colorNo = (i > numOfTones) ? numOfTones - (i - numOfTones) : i;
let delimiter = i == (a.length - 1) ? ')' : ',';
let s = `${(i < 1) ? `repeating-linear-gradient(90deg,` : `${scheme[colorNo - 1]} ${breakPoint}%${delimiter}`}`;
return s;
}).join(' ');
root.style.setProperty('--grad', grad);
document.querySelector('div').classList.add('animate');
:root {
--color: #545454;
}
html {
height: 100vh;
}
body {
margin: 0;
padding: 0;
height: 100%;
}
div {
width: 100%;
height: 100%;
}
.animate {
background: var(--grad);
background-position: 400% 50%;
background-size: 400% 400%;
animation: gradient-animation 35s linear infinite;
}
#keyframes gradient-animation {
0% { background-position: 400% 50% }
100% { background-position: 0% 50% }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/color-js/1.0.1/color.min.js"></script>
<div></div>

I solved the issue by using a timing function to control the drop value. Don't know why I didn't think of this before, but thank you anyway.
-(Math.cos(Math.PI * x) - 1)
It now generates wider end points.
const root = document.documentElement;
const Color = net.brehaut.Color;
const dropBy = 0.2;
let numOfTones = 500;
let color = Color(getComputedStyle(document.documentElement).getPropertyValue('--color'));
let incrementer = 0;
let scheme = [...Array(numOfTones).keys()].map((i, _, a) => {
incrementer += 1 / numOfTones;
let drop = dropBy * -(Math.cos(Math.PI * incrementer) - 1) / 2;
let tone = (i != 0) ? color.devalueByAmount(drop) : color;
return tone;
});
let grad = [...Array(numOfTones * 2).keys()].map((i, _, a) => {
let numOfSteps = a.length - 2;
let breakPoint = (((100 / 2) / (numOfSteps)) * (i-1)).toFixed(6);
let colorNo = (i > numOfTones) ? numOfTones - (i - numOfTones) : i;
let delimiter = i == (a.length - 1) ? ')' : ',';
let s = `${(i < 1) ? `repeating-linear-gradient(90deg,` : `${scheme[colorNo - 1]} ${breakPoint}%${delimiter}`}`;
return s;
}).join(' ');
root.style.setProperty('--grad', grad);
document.querySelector('div').classList.add('animate');
:root {
--color: #545454;
}
html {
height: 100vh;
}
body {
margin: 0;
padding: 0;
height: 100%;
}
div {
width: 100%;
height: 100%;
}
.animate {
background: var(--grad);
background-position: 400% 50%;
background-size: 400% 400%;
animation: gradient-animation 35s linear infinite;
}
#keyframes gradient-animation {
0% { background-position: 400% 50% }
100% { background-position: 0% 50% }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/color-js/1.0.1/color.min.js"></script>
<div></div>

Related

How can I effectively pan an entire image programmatically?

I have a 11500x11500 div that consists of 400 images, that obviously overflows the viewport.
I would like to pan around the whole div programmatically.
I want to generate an animation and by the time the animation is over, the whole of the div must have been panned across the viewport, top to bottom, left to right.
Right now, I am "splitting" my 11500x1500 div into tiles. The maximum width and height of each tile is the width and height of the viewport.
I store the coordinates of each tile and then I randomly choose one, pan it left-to-right and then move on to the next one.
I would like to know:
whether my method is correct or whether I am missing something in my calculations/approach and it could be improved. Given the size, it is hard for me to tell whether I'm actually panning the whole of the div after all
whether I can make the panning effect feel more "organic"/"natural". In order to be sure that the whole div is eventually panned, I pick each tile and pan it left-to-right, move on to the next one etc. This feels kind of rigid and too formalised. Is there a way to pan at let's say an angle or with a movement that is even more random and yet be sure that the whole div will eventually be panned ?
Thank in advance for any help.
This is the jsfiddle and this is the code (for the sake of the example/test every "image" is actually a div containing its index as text):
function forMs(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}
let container = document.getElementById('container')
let {
width,
height
} = container.getBoundingClientRect()
let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height
let i = 0
while (i < 400) {
// adding "image" to the container
let image = document.createElement('div')
// add some text to the "image"
// to know what we're looking at while panning
image.innerHTML = ''
let j = 0
while (j < 100) {
image.innerHTML += ` ${i + 1}`
j++
}
container.appendChild(image)
i++
}
let coords = []
let x = 0
while (x < width) {
let y = 0
while (y < height) {
coords.push({
x,
y
})
y += window.innerHeight
}
x += window.innerWidth
}
async function pan() {
if (!coords.length) {
return;
}
let randomIdx = Math.floor(Math.random() * coords.length)
let [randomCoord] = coords.splice(randomIdx, 1);
console.log(coords.length)
container.classList.add('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
// move to new yet-unpanned area
container.style.top = Math.max(-randomCoord.y, minTop) + 'px'
container.style.left = Math.max(-randomCoord.x, minLeft) + 'px'
// wait (approx.) for transition to end
await forMs(2500)
container.classList.remove('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
//pan that area
let newLeft = -(randomCoord.x + window.innerWidth)
if (newLeft < minLeft) {
newLeft = minLeft
}
container.style.left = newLeft + 'px'
// wait (approx.) for transition to end
await forMs(4500)
// move on to next random area
await pan()
}
pan()
html,
body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: auto;
}
* {
margin: 0;
padding: 0;
}
#container {
position: absolute;
top: 0;
left: 0;
text-align: left;
width: 11500px;
height: 11500px;
transition: all 4s ease-in-out;
transition-property: top left;
font-size: 0;
}
#container.fast {
transition-duration: 2s;
}
#container div {
display: inline-block;
height: 575px;
width: 575px;
border: 1px solid black;
box-sizing: border-box;
font-size: 45px;
overflow: hidden;
word-break: break-all;
}
<div id="container"></div>
I think following improvements can be made:
Hide overflow on html and body so user can not move scrollbar and disturb the flow.
Calculate minLeft and minTop every time to account for window resizing. You might need ResizeObserver to recalculate things.
Increase transition times to avoid Cybersickness. In worse case RNG will pick bottom right tile first so your container will move the longest in 2seconds! Maybe, you can zoom-out and move then zoom-in then perform pan. Or use any serpentine path which will make shorter jumps.
Performance improvements:
Use transform instead of top, left for animation.
Use will-change: transform;. will-change will let browser know what to optimize.
Use translate3D() instead of translate(). ref
Use requestAnimationFrame. Avoid setTimeout, setInterval.
This is an old but good article: https://www.paulirish.com/2012/why-moving-elements-with-translate-is-better-than-posabs-topleft/
Modified code to use transform:
function forMs(time) {
return new Promise((resolve) => {
setTimeout(() => {
resolve()
}, time)
})
}
let container = document.getElementById('container')
let stat = document.getElementById('stats');
let {
width,
height
} = container.getBoundingClientRect()
let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height
let i = 0
while (i < 400) {
// adding "image" to the container
let image = document.createElement('div')
// add some text to the "image"
// to know what we're looking at while panning
image.innerHTML = ''
let j = 0
while (j < 100) {
image.innerHTML += ` ${i + 1}`
j++
}
container.appendChild(image)
i++
}
let coords = []
let x = 0
while (x < width) {
let y = 0
while (y < height) {
coords.push({
x,
y
})
y += window.innerHeight
}
x += window.innerWidth
}
let count = 0;
async function pan() {
if (!coords.length) {
stat.innerText = 'iteration: ' +
(++count) + '\n tile# ' + randomIdx + ' done!!';
stat.style.backgroundColor = 'red';
return;
}
let minLeft = window.innerWidth - width
let minTop = window.innerHeight - height
let randomIdx = Math.floor(Math.random() * coords.length);
randomIdx = 1; //remove after debugging
let [randomCoord] = coords.splice(randomIdx, 1);
stat.innerText = 'iteration: ' +
(++count) + '\n tile# ' + randomIdx;
console.log(coords.length + ' - ' + randomIdx)
container.classList.add('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
// move to new yet-unpanned area
let yy = Math.max(-randomCoord.y, minTop);
let xx = Math.max(-randomCoord.x, minLeft);
move(xx, yy);
// wait (approx.) for transition to end
await forMs(2500)
container.classList.remove('fast')
// update style in new thread so new transition-duration is applied
await forMs(10)
//pan that area
let newLeft = -(randomCoord.x + window.innerWidth)
if (newLeft < minLeft) {
newLeft = minLeft
}
xx = newLeft;
//container.style.left = newLeft + 'px'
move(xx, yy);
// wait (approx.) for transition to end
await forMs(4500)
// move on to next random area
await pan()
}
pan()
function move(xx, yy) {
container.style.transform = "translate3D(" + xx + "px," + yy + "px,0px)";
}
html,
body {
position: relative;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#container {
text-align: left;
width: 11500px;
height: 11500px;
transition: all 4s ease-in-out;
transition-property: transform;
font-size: 0;
will-change: transform;
}
#container.fast {
transition-duration: 2s;
}
#container div {
display: inline-block;
height: 575px;
width: 575px;
border: 1px solid black;
box-sizing: border-box;
font-size: 45px;
overflow: hidden;
word-break: break-all;
}
#stats {
border: 2px solid green;
width: 100px;
background-color: lightgreen;
position: fixed;
opacity: 1;
top: 0;
left: 0;
z-index: 10;
}
<div id=stats>iteration: 1 tile# 11</div>
<div id="container"></div>
Note I haven't implemented everything in above snippet.

How to decrease anime time when it is out of screen

I have a anime whose duration decreases when the black container crosses red, but this creates glitches. Can there be any method to remove the glitch in it.
I tried to delay the changes until path is completed by red but still faces the glitches.
delayInAnimeSub = ourVillanAnimeDuration * (ourVillanFigXValue / window.innerWidth)
animeDelayAmount = Math.abs(delayInAnimeSub.toFixed(2) - 0.2).toFixed(2);
I calculated the leftover distance of red from the left side and get the duration to complete that distance and added that to delay. But it still show glitches.
Here what happens in below snippet
Black box is hero which is controlled by space(to jump), <(to move left), >(to move right).
Red is villan(demon) which have animation to move from right side to left side with some duration in animation.
Whenever hero passes the red, red becomes faster(by trying to reduce animation duration), but with this red has glitches and start from anywhere(not from the place it should be next).
I want to increase speed of red but facing the glitches so tried to delay the change in animation duration until red crosses the screen but that didn't seems to work
let ourHeroFig = document.getElementById("ourHero");
let ourVillanFig = document.getElementById("obstacleBar");
let gameScoreDigits = document.getElementById("gameScoreDigits");
let valueXCoordinate = "";
let obstacleBarCrossed = true;
document.body.addEventListener('keydown', function(e) {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
if (e.code === "ArrowRight") {
valueXCoordinate = ourHeroFigXValue + 100;
} else if (e.code === "KeyA" || e.code === "ArrowLeft") {
if (ourHeroFigXValue > ourHeroFig.offsetWidth + 90) {
valueXCoordinate = ourHeroFigXValue - 100;
} else {
valueXCoordinate = 0;
}
} else if (e.code === "Space") {
ourHeroFig.classList.add("animateHero");
setTimeout(function() {
ourHeroFig.classList.remove("animateHero");
}, 700)
}
changePosition();
})
function changePosition() {
ourHeroFig.style.left = valueXCoordinate + 'px'
}
let delayInAnimeSub = ""
setInterval(
function() {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
let ourVillanFigXValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('left'));
let ourVillanFigYValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('bottom'));
let gameOverValueX = Math.abs(ourVillanFigXValue - ourHeroFigXValue);
let gameOverValueY = Math.abs(ourVillanFigYValue - ourHeroFigYValue);
if (gameOverValueX < ourVillanFig.offsetWidth && gameOverValueY < ourVillanFig.offsetHeight) {
console.log("yes touched");
ourVillanFig.classList.remove("animateVillan");
obstacleBarCrossed = false;
} else if (obstacleBarCrossed && gameOverValueX < ourVillanFig.offsetWidth) {
ourVillanAnimeDuration = parseFloat(getComputedStyle(ourVillanFig).getPropertyValue('animation-duration'));
delayInAnimeSub = ourVillanAnimeDuration * (ourVillanFigXValue / window.innerWidth)
animeDelayAmount = Math.abs(delayInAnimeSub.toFixed(2) - 0.2).toFixed(2);
console.log(animeDelayAmount, ourVillanAnimeDuration, ourVillanFigXValue)
if (ourVillanAnimeDuration <= 2) {
ourVillanAnimeDuration = 2
}
setTimeout(() => {
ourVillanFig.style.animationDuration = ourVillanAnimeDuration - 0.1 + "s";
}, animeDelayAmount);
}
// console.log(gameOverValueX,gameOverValueY)
}, 10);
#ourHero {
width: 20px;
height: 100px;
background-color: black;
position: fixed;
bottom: 0;
left: 0;
transition: 0.1s;
}
.animateHero {
animation: animateHero 0.7s linear;
}
#keyframes animateHero {
0% {
bottom: 0;
}
50% {
bottom: 350px;
}
100% {
bottom: 0;
}
}
#obstacleBar {
width: 20px;
height: 100px;
background-color: red;
position: fixed;
bottom: 0;
left: 50vw;
}
.animateVillan {
animation: animateVillan 5s linear infinite;
}
#keyframes animateVillan {
0% {
left: 110vw;
}
100% {
left: 0;
}
}
<div id="ourHero"></div>
<div id="obstacleBar" class="animateVillan"></div>
Thanks for help in advance
One way is to use Animation.playbackRate but it is still experimental.
let VillRate = 1
...
// inside the collision check
if (VillRate < 20) {
ourVillanFig.getAnimations()[0].playbackRate += 0.2
VillRate += 0.5
}
In the below snippet, red speed will increase untill a certain point everytime it touches black. You can adjust that behavior as you need.
let ourHeroFig = document.getElementById('ourHero')
let ourVillanFig = document.getElementById('obstacleBar')
let gameScoreDigits = document.getElementById('gameScoreDigits')
let valueXCoordinate = ''
let obstacleBarCrossed = true
let VillRate = 1
document.body.addEventListener('keydown', function(e) {
let ourHeroFigXValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('left')
)
let ourHeroFigYValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('bottom')
)
if (e.code === 'ArrowRight') {
valueXCoordinate = ourHeroFigXValue + 100
} else if (e.code === 'KeyA' || e.code === 'ArrowLeft') {
if (ourHeroFigXValue > ourHeroFig.offsetWidth + 90) {
valueXCoordinate = ourHeroFigXValue - 100
} else {
valueXCoordinate = 0
}
} else if (e.code === 'Space') {
ourHeroFig.classList.add('animateHero')
setTimeout(function() {
ourHeroFig.classList.remove('animateHero')
}, 700)
}
changePosition()
})
function changePosition() {
ourHeroFig.style.left = valueXCoordinate + 'px'
}
let delayInAnimeSub = ''
setInterval(function() {
let ourHeroFigXValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('left')
)
let ourHeroFigYValue = parseInt(
getComputedStyle(ourHeroFig).getPropertyValue('bottom')
)
let ourVillanFigXValue = parseInt(
getComputedStyle(ourVillanFig).getPropertyValue('left')
)
let ourVillanFigYValue = parseInt(
getComputedStyle(ourVillanFig).getPropertyValue('bottom')
)
let gameOverValueX = Math.abs(ourVillanFigXValue - ourHeroFigXValue)
let gameOverValueY = Math.abs(ourVillanFigYValue - ourHeroFigYValue)
if (
gameOverValueX < ourVillanFig.offsetWidth &&
gameOverValueY < ourVillanFig.offsetHeight
) {
if (VillRate < 20) {
ourVillanFig.getAnimations()[0].playbackRate += 0.2
VillRate += 0.5
}
}
}, 10)
#ourHero {
width: 20px;
height: 100px;
background-color: black;
position: fixed;
bottom: 0;
left: 0;
transition: 0.1s;
}
.animateHero {
animation: animateHero 0.7s linear;
}
#keyframes animateHero {
0% {
bottom: 0;
}
50% {
bottom: 350px;
}
100% {
bottom: 0;
}
}
#obstacleBar {
width: 20px;
height: 100px;
background-color: red;
position: fixed;
bottom: 0;
left: 50vw;
}
.animateVillan {
animation: animateVillan 5s linear infinite;
}
#keyframes animateVillan {
0% {
left: 110vw;
}
100% {
left: 0;
}
}
<div id="ourHero"></div>
<div id="obstacleBar" class="animateVillan"></div>
I tried this:
if (ourVillanFigXValue < 10) {
ourVillanFig.style.animationDuration = ourVillanAnimeDuration - 0.1 + "s";
}
It works ok but when time get reduced to 4s and after it, the red don't start from the end but start in between. Almost start from middle at 3s
let ourHeroFig = document.getElementById("ourHero");
let ourVillanFig = document.getElementById("obstacleBar");
let gameScoreDigits = document.getElementById("gameScoreDigits");
let valueXCoordinate = "";
let obstacleBarCrossed = true;
document.body.addEventListener('keydown', function(e) {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
if (e.code === "ArrowRight") {
valueXCoordinate = ourHeroFigXValue + 100;
} else if (e.code === "KeyA" || e.code === "ArrowLeft") {
if (ourHeroFigXValue > ourHeroFig.offsetWidth + 90) {
valueXCoordinate = ourHeroFigXValue - 100;
} else {
valueXCoordinate = 0;
}
} else if (e.code === "Space") {
ourHeroFig.classList.add("animateHero");
setTimeout(function() {
ourHeroFig.classList.remove("animateHero");
}, 700)
}
changePosition();
})
function changePosition() {
ourHeroFig.style.left = valueXCoordinate + 'px'
}
let delayInAnimeSub = ""
setInterval(
function() {
let ourHeroFigXValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('left'));
let ourHeroFigYValue = parseInt(getComputedStyle(ourHeroFig).getPropertyValue('bottom'));
let ourVillanFigXValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('left'));
let ourVillanFigYValue = parseInt(getComputedStyle(ourVillanFig).getPropertyValue('bottom'));
let gameOverValueX = Math.abs(ourVillanFigXValue - ourHeroFigXValue);
let gameOverValueY = Math.abs(ourVillanFigYValue - ourHeroFigYValue);
if (ourVillanFigXValue < 10) {
ourVillanFig.style.animationDuration = ourVillanAnimeDuration - 0.1 + "s";
}
if (gameOverValueX < ourVillanFig.offsetWidth && gameOverValueY < ourVillanFig.offsetHeight) {
console.log("yes touched");
ourVillanFig.classList.remove("animateVillan");
obstacleBarCrossed = false;
} else if (obstacleBarCrossed && gameOverValueX < ourVillanFig.offsetWidth) {
ourVillanAnimeDuration = parseFloat(getComputedStyle(ourVillanFig).getPropertyValue('animation-duration'));
console.log(ourVillanFigXValue < 0, ourVillanAnimeDuration)
if (ourVillanAnimeDuration <= 2) {
ourVillanAnimeDuration = 2
}
}
// console.log(gameOverValueX,gameOverValueY)
}, 10);
#ourHero {
width: 20px;
height: 180px;
background-color: black;
position: fixed;
bottom: 0;
left: 0;
transition: 0.1s;
}
.animateHero {
animation: animateHero 0.7s linear;
}
#keyframes animateHero {
0% {
bottom: 0;
}
50% {
bottom: 350px;
}
100% {
bottom: 0;
}
}
#obstacleBar {
width: 20px;
height: 180px;
background-color: red;
position: fixed;
bottom: 0;
left: 50vw;
}
.animateVillan {
animation: animateVillan 5s linear infinite;
}
#keyframes animateVillan {
0% {
left: 110vw;
}
100% {
left: 0;
}
}
<div id="ourHero"></div>
<div id="obstacleBar" class="animateVillan"></div>

css transition issue with slider

i have provided all my codes here all seems good the problem is when i change from last slide to first or the opposite in both case all slides flips differently instead rotating like with other sildes
render() function is the place where all co-ordinates are calculating
i think the issue is with CSS transition since its the main thing which is animating slides, i dont see any easy fix for this if you can find any tell me please looking for easy solution or possibility without messing too much with co-ordinates . thanks in advance
class slider {
constructor(){
if(typeof arguments[0] != "undefined"){
this.init()
arguments[0].appendChild(this.html)
this.calculate()
this.render(1)
}
else{this.init()}
}
init(){
let obj = this
this.html = `
<div class="root border-2 p-5 border-black m-auto">
<div class="each border border-black absolute text-center">item name 1</div>
</div>
`
let el = document.createElement("div")
el.innerHTML = this.html
this.html = el.querySelector("*")
this.root = this.html
let eEl = this.root.querySelector(".each")
this.eachTemp = eEl.cloneNode(true)
eEl.remove()
this.fillBoxes(6)
this.html.addEventListener("click",function(e){
obj.onclick(obj,e)
})
}
onclick(obj,e){
let t = e.target
if(t.closest(".each")){
obj.render(t.index)
}
}
fillBoxes(num){
for(let i=0;i<num;i++){
let newEl = this.eachTemp.cloneNode(true)
newEl.index = i+1
newEl.innerText = "Item " + (i+1)
this.html.appendChild(newEl)
}
this.els = this.html.querySelectorAll(".each")
}
calculate(){
this.eCor = this.els[0].getClientRects()[0]
this.mag = this.eCor.width
this.per = Math.PI / (this.els.length / 2)
this.deg = this.rtod(this.per)
}
render(index){
this.els.forEach((each,i)=>{
let rad = (this.per * i) - this.per*(index-1),
x = Math.sin(rad)*this.mag,
y = Math.cos(rad)*this.mag
if(i+1 == index){each.classList.add("active")}
else{each.classList.remove("active")}
each.style.transform = `translate3d(${x}px,0%,${y}px)`
each.style.transform += `rotateY(${this.rtod(rad)}deg)`
})
}
rtod(radians){
return radians * (180/Math.PI)
}
}
const s = new slider(document.body)
.each{
height: 250px;
width: 250px;
background: skyblue;
opacity: 0.8;
transform-origin: 50% 50%;
transform-style: preserve-3d;
user-select: none;
filter: brightness(80%);
transition: all 1s cubic-bezier(0, 1.22, 0.53, 1.02);
/* box-shadow: 0px 0px 10px 1px black; */
}
.each:nth-child(odd){background:lightsalmon}
.root{
height: 280px;
width: 280px;
position: relative;
margin-top: 10%;
perspective: 1000px;
transform-style: preserve-3d;
transition: transform 0.5s;
/* animation: loop 4s infinite linear; */
}
.each:hover{
z-index: 1000 !important;
filter:brightness(110%);
}
.active{
opacity: 1 !important;
filter:brightness(100%) !important;
}
<link href="https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css" rel="stylesheet">
When you switch between 1 and 6 slides, your calculated rotatedY value changes from 0deg to 300deg (or from 300deg to 0deg). But actually you need only ±60deg update. So you need a mechanism to increase/decrease your rotatedY endlessly in both ways, for example 240 -> 300 -> 360 -> 420 -> 480 ... (not 240 -> 300 -> 0 -> 60 -> ...)
I suggest you to have these properties in your class:
currentIndex = 1;
radOffset = 0;
Then, when you click on element, you need to calculate radOffset (it increases/decreases endlessly):
const totalElements = this.els.length;
const rightDiff = t.index > this.currentIndex ? t.index - this.currentIndex : totalElements + t.index - this.currentIndex;
const leftDiff = totalElements - rightDiff;
const closestDiff = rightDiff < leftDiff ? - rightDiff : leftDiff;
this.currentIndex = t.index;
this.radOffset += this.per * closestDiff;
Then use it in your render function:
let rad = (this.per * i) + this.radOffset,
class slider {
currentIndex = 1;
radOffset = 0;
rotateOffset = 0;
constructor(){
if(typeof arguments[0] != "undefined"){
this.init()
arguments[0].appendChild(this.html)
this.calculate()
this.render(1)
}
else{this.init()}
}
init(){
let obj = this
this.html = `
<div class="root border-2 p-5 border-black m-auto">
<div class="each border border-black absolute text-center">item name 1</div>
</div>
`
let el = document.createElement("div")
el.innerHTML = this.html
this.html = el.querySelector("*")
this.root = this.html
let eEl = this.root.querySelector(".each")
this.eachTemp = eEl.cloneNode(true)
eEl.remove()
this.fillBoxes(6)
this.html.addEventListener("click",function(e){
obj.onclick(obj,e)
})
}
onclick(obj,e){
let t = e.target
if(t.closest(".each")){
const totalElements = this.els.length;
const rightDiff = t.index > this.currentIndex ? t.index - this.currentIndex : totalElements + t.index - this.currentIndex;
const leftDiff = totalElements - rightDiff;
const closestDiff = rightDiff < leftDiff ? - rightDiff : leftDiff;
this.currentIndex = t.index;
this.radOffset += this.per * closestDiff;
obj.render(t.index);
}
}
fillBoxes(num){
for(let i=0;i<num;i++){
let newEl = this.eachTemp.cloneNode(true)
newEl.index = i+1
newEl.innerText = "Item " + (i+1)
this.html.appendChild(newEl)
}
this.els = this.html.querySelectorAll(".each")
}
calculate(){
this.eCor = this.els[0].getClientRects()[0]
this.mag = this.eCor.width
this.per = Math.PI / (this.els.length / 2)
this.deg = this.rtod(this.per)
}
render(index, rotateDiff){
this.els.forEach((each,i)=>{
let rad = (this.per * i) + this.radOffset,
x = Math.sin(rad)*this.mag,
y = Math.cos(rad)*this.mag
if(i+1 == index){each.classList.add("active")}
else{each.classList.remove("active")}
each.style.transform = `translate3d(${x}px,0%,${y}px)`
each.style.transform += `rotateY(${this.rtod(rad)}deg)`
})
}
rtod(radians){
return Math.round(radians * (180/Math.PI));
}
}
const s = new slider(document.body)
.each{
height: 250px;
width: 250px;
background: skyblue;
opacity: 0.8;
transform-origin: 50% 50%;
transform-style: preserve-3d;
user-select: none;
filter: brightness(80%);
transition: all 1s cubic-bezier(0, 1.22, 0.53, 1.02);
/* box-shadow: 0px 0px 10px 1px black; */
}
.each:nth-child(odd){background:lightsalmon}
.root{
height: 280px;
width: 280px;
position: relative;
margin-top: 10%;
perspective: 1000px;
transform-style: preserve-3d;
transition: transform 0.5s;
/* animation: loop 4s infinite linear; */
}
.each:hover{
z-index: 1000 !important;
filter:brightness(110%);
}
.active{
opacity: 1 !important;
filter:brightness(100%) !important;
}
<link href="https://unpkg.com/tailwindcss#^2/dist/tailwind.min.css" rel="stylesheet">

How do I create png button animation with javascript?

I'm a graphic designer in Portugal, used to work with code everyday, like css, html and a bit javascript and php. I am currently developing an interactive logo button, but it has to be PNG to look the way I want. This is the javascript code on html (image is hosted in my website):
I want to create a mouseclick start and stop on last/first frame, not a infinite loop like this, and reversed animation after click to open/close. Basically, the lock and unlock of the padlock.
The point of this animation is to open a menu nav-bar under the logo. Can you help me?
My code:
var cSpeed = 5;
var cWidth = 200;
var cHeight = 145;
var cTotalFrames = 7;
var cFrameWidth = 200;
var cImageSrc = 'https://www.studiogo.net/sprites.png';
var cImageTimeout = false;
var cIndex = 0;
var cXpos = 0;
var SECONDS_BETWEEN_FRAMES = 0;
function startAnimation() {
document.getElementById('loaderImage').style.backgroundImage = 'url(' + cImageSrc + ')';
document.getElementById('loaderImage').style.width = cWidth + 'px';
document.getElementById('loaderImage').style.height = cHeight + 'px';
//FPS = Math.round(100/(maxSpeed+2-speed));
FPS = Math.round(100 / cSpeed);
SECONDS_BETWEEN_FRAMES = 1 / FPS;
setTimeout('continueAnimation()', SECONDS_BETWEEN_FRAMES / 1000);
}
function continueAnimation() {
cXpos += cFrameWidth;
//increase the index so we know which frame of our animation we are currently on
cIndex += 1;
//if our cIndex is higher than our total number of frames, we're at the end and should restart
if (cIndex >= cTotalFrames) {
cXpos = 0;
cIndex = 0;
}
document.getElementById('loaderImage').style.backgroundPosition = (-cXpos) + 'px 0';
setTimeout('continueAnimation()', SECONDS_BETWEEN_FRAMES * 1000);
}
function imageLoader(s, fun) //Pre-loads the sprites image
{
clearTimeout(cImageTimeout);
cImageTimeout = 0;
genImage = new Image();
genImage.onload = function() {
cImageTimeout = setTimeout(fun, 0)
};
genImage.onerror = new Function('alert(\'Could not load the image\')');
genImage.src = s;
}
//The following code starts the animation
new imageLoader(cImageSrc, 'startAnimation()');
<div id="loaderImage"></div>
Please, see if this is what you want.
$(document).ready(function () {
$(".lock").click(function () {
var $self = $(this);
if ($self.hasClass("closed")) {
$self.removeClass("close");
setTimeout(function () {
$self.addClass("open").removeClass("closed");
}, 100);
} else {
$self.removeClass("open");
setTimeout(function () {
$self.addClass("close").addClass("closed");
}, 100);
}
});
});
div.lock {
background-image: url('https://www.studiogo.net/sprites.png');
width: 200px;
height: 145px;
background-position: 0 center;
background-repeat: no-repeat;
}
div.closed {
background-position: -1200px center;
}
div.close {
animation: close-animation 300ms steps(6, start); // 1200px / 200px = 6
}
div.open {
animation: close-animation 300ms steps(6, end); // 1200px / 200px = 6
animation-fill-mode: backwards;
animation-direction: reverse;
}
#keyframes close-animation {
from {
background-position: 0 center;
}
to {
background-position: -1200px center;
}
}
<div class="lock closed">
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

Array slide show not working smoothly with javascript

Written some javascript (very new to this) to center the div and make it full screen adjusting as the window does, that works fine but now I have added some script I found online to transition from one image to another using an array. They seem to be contradicting each other messing up the animation, the biggest problem is when I resize the window. Here is my jsfiddle so you can see for yourself. Thanks in advance.
http://jsfiddle.net/xPZ3W/
function getWidth() {
var w = window.innerWidth;
x = document.getElementById("wrapper");
x.style.transition = "0s linear 0s";
x.style.width= w +"px";
}
function moveHorizontal() {
var w = window.innerWidth;
x = document.getElementById("wss");
x.style.transition = "0s linear 0s";
x.style.left= w / 2 -720 +"px" ;
}
function moveVertical() {
var h = window.innerHeight;
x = document.getElementById("wss");
x.style.transition = "0s linear 0s";
x.style.top= h / 2 -450 +"px" ;
}
var i = 0;
var wss_array = ['http://cdn.shopify.com/s/files/1/0259/8515/t/14/assets/slideshow_3.jpg? 48482','http://cdn.shopify.com/s/files/1/0259/8515/t/14/assets/slideshow_5.jpg?48482'];
var wss_elem;
function wssNext(){
i++;
wss_elem.style.opacity = 0;
if(i > (wss_array.length - 1)){
i = 0;
}
setTimeout('wssSlide()',1000);
}
function wssSlide(){
wss_elem = document.getElementById("wss")
wss_elem.innerHTML = '<img src="'+wss_array[i]+'">';
wss.style.transition = "0.5s linear 0s";
wss_elem.style.opacity = 1;
setTimeout('wssNext()',3000);
}
So I whipped up this JSFiddle from scratch, and I hope it helps out. Pure CSS transitions from class to class using your array URLs to switch among the pictures.
Basically this just advances the "active" class to the next one everytime it's called, provided the first picture is set to "active" class.
var pics = document.getElementById('slideshow').children,
active = 0;
function slideshow() {
for (var i = 0; i < pics.length; i++) {
if (i == active && pics[i].className == "active") {
console.log(i, active, (active + 1) % pics.length);
active = (active + 1) % pics.length;
}
pics[i].className = "";
}
pics[active].className = "active";
setTimeout(slideshow, 2000);
}
setTimeout(slideshow, 2000);
And here's the CSS, which absolutely positions the container, and hides all its children unless it has the active class, to which it will transition smoothly.
#slideshow {
position: absolute;
top: 20%;
bottom: 20%;
left: 20%;
right: 20%;
}
#slideshow img {
position: absolute;
max-height: 100%;
max-width: 100%;
opacity: 0;
transition: opacity 1s linear;
}
#slideshow .active {
opacity: 1;
}

Categories