I am trying to build a simple web element that resembles the wheel contestants spin on The Price is Right (sample video). My question is: is there a more efficient way to achieve this effect than what I've cobbled together below?
My first attempt at this functionality basically decrements the translateY property of an element that contains the elements to be spun [codepen]:
setInterval(shiftCards, 100)
var translateY = -60,
cardIdx = 0,
startCards = 60,
wrap = document.querySelector('.wrap');
for (var i=0; i<startCards; i++) {
addCard()
}
function addCard() {
var div = document.createElement('div');
div.className = 'card';
div.style.top = (cardIdx * 12) + 'px';
wrap.appendChild(div);
cardIdx++;
}
function shiftCards() {
wrap.style.transform = 'translateY(' + translateY + 'px)';
translateY -= 12;
var cards = wrap.querySelectorAll('.card');
if (cards.length >= startCards) {
cards[0].parentNode.removeChild(cards[0]);
addCard();
}
}
.cards {
width: 80px;
height: 200px;
overflow: hidden;
background: #aaa;
}
.wrap {
position: relative;
transition: transform .25s;
}
.card {
width: 100%;
height: 10px;
margin: 2px 0;
background: red;
position: absolute;
left: 0;
right: 0;
}
<div class='cards'>
<div class='wrap'>
</div>
</div>
Is there a more efficient way to achieve this functionality? Can I create an element with n children and actually just spin them in the Z-dimension, rather than create an artificial spin as I've done above? Any guidance others can offer on this question will be very appreciated!
You can use css-animations for this: (They are probably more efficient)
/* only alignment */
body, html {
height: 100%;
margin: 0;
padding: 0;
}
.wrapper {
display:flex;
justify-content:center;
align-items:center;
height: 100%;
}
/* actual spinning */
img {
animation: spin 5s infinite linear;
/*
spin: the keyframes (=animation) we want to have
5s: how long it should take for one iteration
infinite: how often should it repeat
linear: the easing between the different key frames You can also try 'ease'
*/
}
#keyframes spin {
0% {transform: translateY(-60px);} /* Starting point */
100% {transform: translateY(-360px);} /* end point */
}
#-webkit-keyframes spin {
0% {transform: translateY(-60px);}
100% {transform: translateY(-360px);}
}
<div class="wrapper">
<img src="https://www.placecage.com/300/100" class="spin">
</div>
Related
I am trying to apply 'opacity / sliding to left' for my automatic slider but I couldn't make it. Please help me with this.
let images = ["images/a.jpg", "images/b.jpg", "images/c.jpg", "images/d.jpg", ];
let i = 0;
function changeImg() {
document.querySelector("#slider img").src = images[i];
if (i < images.length - 1) {
i++;
} else {
i = 0;
}
setTimeout("changeImg()", 1000);
}
onload(changeImg());
#slider img {
width: 100vw;
height: 60vh;
transition: opacity 2s ease-out;
}
<section id="slider">
<img name="slide" width="400" height="200" />
</section>
The initial requirement is for the image to fade and another one be shown.
This can be achieved by placing the images one on top of the other (position absolute) and opacity 0 with a transition set on opacity. When it is a particular images turn to be shown the image before it is set to opacity 0 and the new image has its opacity set to 1.
let images = ["https://picsum.photos/id/1015/300/300", "https://picsum.photos/id/1016/200/300", "https://picsum.photos/id/1018/300/200.jpg", "https://picsum.photos/id/1022/400/300", ];
const slider = document.querySelector('#slider');
const imgEls = [];
images.forEach(image => {
let imgEl = document.createElement('img');
imgEl.src = image;
imgEls.push(imgEl);
slider.appendChild(imgEl);
});
let i = 0;
function changeImg() {
imgEls[i].style.opacity = 0;
if (i < images.length - 1) {
i++;
} else {
i = 0;
}
imgEls[i].style.opacity = 1;
setTimeout("changeImg()", 1000);
}
window.onload = changeImg;
#slider {
width: 100vw;
height: 60vh;
position: relative;
isplay: felx;
justify-content: center;
align-items: center;
}
#slider img {
display: inline-block;
width: 100vw;
height: 60vh;
object-fit: contain;
transition: opacity 2s ease-out;
opacity: 0;
position: absolute;
top: 0;
left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<section id="slider">
<https://stackoverflow.com/questions/69870264/how-to-add-css-transitions-to-automatic-slider#img name="slide" width="400" height="200" />
</section>
While the action seems to correspond to what was discussed in the comments it doesn't make a real 'slider' and the question also talks of sliding to the left using CSS.
This snippet takes a slightly different approach to the question's code in that once the initial set up of the slides has been done using Javascript everything else is done via CSS. This has the advantage that it's possible the system can just hand the animation to the GPU without needing to interrupt back to the main program on a timeout.
Each image is located at left 100% (ie just off screen to the right) and it is given the same animation as the other - move to the center, wait and then move out to the left - opacity fading in and out respectively. The images however start animating at staggered times so the sliding looks continuous.
const slider = document.querySelector('#slider');
const images = ["https://picsum.photos/id/1015/300/300", "https://picsum.photos/id/1016/200/300", "https://picsum.photos/id/1018/300/200", "https://picsum.photos/id/1022/400/300"];
let div;
let count = 0;
images.forEach(image => {
div = document.createElement('div');
div.style.animationDelay = (count * 2) + 's';
count++;
div.style.setProperty('--img', 'url(' + image + ')');
slider.appendChild(div);
});
slider.style.setProperty('--n', count);
#slider {
overflow: hidden;
width: 100vw;
height: 60vh;
position: relative;
}
#slider div {
width: 100%;
height: 100%;
position: absolute;
left: 0;
top: 0;
opacity: 0;
display: inline-block;
background-size: contain;
background-position: center center;
background-repeat: no-repeat;
background-image: var(--img);
animation-name: slide;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-duration: calc(var(--n) * 2s);
animation-fill-mode: forwards;
}
#keyframes slide {
0% {
opacity: 0;
transform: translateX(100vw);
}
12.5% {
opacity: 1;
transform: translateX(0);
}
25% {
opacity: 1;
transform: translateX(0);
}
37.5%,
100% {
opacity: 0;
transform: translateX(-100vw);
}
}
<div id="slider"></div>
Note that the keyframes settings assume 4 images. The % values would have to be changed for more or fewer. Unfortunately CSS does not allow variables as % values in keyframes so the JS would ideally find the number of images and create the correct keyframes.
I'm trying to build out a super simple Super Mario-style game using just HTML, CSS and JS.
I'm not using <canvas> just building the whole thing out in simple divs, and getting the movement of the background and characters with CSS Animations.
The part where I'm running into issues is getting collision detection between the character and the monsters. Basically what I want to happen is if the character at all touches the monster, the game is over, and you avoid the monsters by jumping.
I have tried different snippets of JS for div collision detection, but the issue there is the code returns the static X and Y positions of the div, and not that of each keyframe in the CSS animation.
Doing some digging, I found a way for the code to track the X and Y of each keyframe by adding an event listener to my monster and character divs:
shadow1.addEventListener('animationiteration', function (event) {
shadowDiv = this.getBoundingClientRect();
console.log(shadowDiv);
});
cont.addEventListener('animationiteration', function (event) {
charDiv = this.getBoundingClientRect();
console.log(charDiv);
});
The above returns all the X and Y positions as the animation is running into the console for both the monster and the character, but the issue I'm running into with this, is that it only returns the keyframes if it's inside the scope of the event listener. I have tried every which way to get the same thing to happen in a global scope, but everytime I do, it returns a value of undefined, and from there I am basically stuck. and not sure how to move forward.
Any help would be appreciated!
Below is the relevant code:
HTML
<div id="char-container"></div>
<div id="shadow-container-1"> </div>
<div id="btn"> Jump! </div>
CSS
#keyframes rtol {
0% { left: 800px; opacity: 1;}
80% { opacity: 1;}
85%{ opacity: 0.75;}
90% { left: -220px; opacity: 0;}
100% { left: -220px; opacity: 0;}
}
#shadow-container-1{
width: 100px;
height: 160px;
position: absolute;
display: block;
bottom: 450px;
left: 800px;
opacity: 1;
animation-name: rtol;
animation-iteration-count: infinite;
animation-direction: normal;
animation-timing-function: linear;
animation-fill-mode: backwards;
#keyframes jump {
from { bottom: -50px; left: 0px; }
to { bottom: 400px; left: 150px; }
}
#char-container{
width: 150px;
height: 200px;
position: relative;
bottom: -50px;
}
JS
let btn = document.getElementById('btn');
let cont = document.getElementById('char-container');
btn.addEventListener('click', function(e){
if(e.target = btn){
cont.style.webkitAnimationPlayState = 'running';
cont.style.webkitAnimation = 'jump 1.25s ease 2';
cont.style.webkitAnimationDirection = 'alternate';
}
});
cont.addEventListener("webkitAnimationEnd", function(e){
cont.style.webkitAnimation = '';
});
let shadow1 = document.getElementById('shadow-container-1');
shadow1.style.webkitAnimationDuration = Math.floor(Math.random() * 15) + 3 + 's';
shadow1.addEventListener('animationiteration', function (event) {
shadowDiv = this.getBoundingClientRect();
console.log(shadowDiv);
});
cont.addEventListener('animationiteration', function (event) {
charDiv = this.getBoundingClientRect();
console.log(charDiv);
});
I am placing a DIV element on the screen using CSS translate. This works fine, except that when the same element is displaced later, the original displacement is discarded.
Set the CSS start position with javascript
div.style.transform ="translate(800px, 400px)";
After setting the starting position randomly with javascript, the CSS animation just resets it back.
CSS Animation
#keyframes testanimation {
0% {
transform: translateY(20px);
}
100% {
transform: translateY(80px);
}
}
How can I combine CSS translations, to take previous displacements into account? In this case, the goal is to have the animation start at randomised locations. The 20px - 80px should be relative.
The best way to do this I would guess is to fetch the previous transform, add something to those values and then apply the new transform.
Another suggestion is to set the position of the element using position, left and top. Using the following code for example:
div.style.position = "absolute";
div.style.left = "800px";
div.style.top = "400px";
That way, the transform would then apply to that position instead of relative to your previous transform.
If it is only about transform, then you need to reset each value in the animation, else it will be overwritten by animation rules.
example:
div {/* translate value reduced to fit in snippet window */
border:solid;
height:40px;
width:40px;
transform:translatex(180px) translatey(140px);
animation:2s testanimation forwards;
}
#keyframes testanimation {
0% {
transform:translatex(180px) translatey(150px)
}
100% {
transform:translatex(180px) translatey(180px)
}
}
<div></div>
Your style to apply to start width should be :
div.style.transform ="translatex(800px) translatey(400px)";
and the animation :
#keyframes testanimation {
0% {
transform:translatex(800px) translatey(420px)
}
100% {
transform:translatex(800px) translatey(480px)
}
}
in order to update only the translatey value
translate or position:relative + coordonates have the same effects/results.
You can also combine them
div {/* value reduced to fit window's demo */
border:solid;
height:40px;
width:40px;
position:relative;
transform:translate(80px,40px);
animation:2s testanimation forwards;
}
#keyframes testanimation {
0% {
top : 20px
}
100% {
top:40px
}
}
<div></div>
the other way round works too :
div {
/* value reduced to fit window's demo */
border: solid;
height: 40px;
width: 40px;
position: relative;
left: 80px;
top: 40px;
animation: 2s testanimation forwards;
}
#keyframes testanimation {
0% {
transform: translatey(20px)
}
100% {
transform: translatey(40px)
}
}
<div></div>
An example of my comment above:
#wrapperDiv {
width: 100px;
height: 100px;
background: green;
transform: translate(400px, 200px) rotate(50deg);
}
#yourDiv {
width: 100px;
height: 100px;
background: red;
animation: testanimation 2s infinite;
}
#keyframes testanimation {
0% {
transform: translateY(20px) rotate(0deg);
}
100% {
transform: translateY(80px) rotate(40deg);
}
}
<div id="wrapperDiv">
<div id="yourDiv">
<div>
<div>
The div#yourDiv will get a transform relative to its parent div#wrapperDiv transform.
I think a more general solution would be to parse the css transform property, as this allows you to keep animating an element without having to worry about its state.
This is the solution I use for this case:
parseTransformMatrix = function(str){
const match_float = "[+\\-]?(?:0|[1-9]\\d*)(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?"
const match_sep = "\\s*,?\\s*"
m = str.match(RegExp(`matrix\\((${match_float}(?:${match_sep}${match_float})*)\\)`))
if(!m || !m.length || m.length < 2) return null
return m[1].match(RegExp(match_float, 'g')).map(x => parseFloat(x))
}
// Test parseTransformMatrix method
console.log(parseTransformMatrix("matrix(1, 0, 0, 1, 96.2351, 309.123)"));
getElementTranslate = function(e){
let t = e.css("transform")
let r = { left: 0, top: 0 }
if(!t) return r
mat = parseTransformMatrix(t)
if(!mat || !mat.length || mat.length != 6) return r
r.left = mat[4]
r.top = mat[5]
return r
}
Here, e is a jQuery element, but you could easily use getPropertyValue instead if you don't want to have this dependency.
Using these functions, you can do something like:
let t = getElementTranslate(e)
e.css({transform:`translate(${t.left+20}px,${t.top+80}px)`})
I have two boxes:
<div class='item' style='transform:translateY(100px)'>
</div>
<div class='item' style='transform:translateY(300px)'>
</div>
They both use the same class:
.item {
position:absolute;
background:red;
animation:float 3s ease-in-out infinite;
width:100px;
height:100px;
}
The animation looks like:
#keyframes float {
from, to { transform: translateY(-10px); }
50% { transform: translateY(10px); }
}
But this makes both boxes go between -10 and 10px. What I'd like is for it to be relative to the current value of the box.
So box1 at y:100px would animate from 90px to 110px
and box2 at y:300px would animate from 290px to 310px
Is it possible to do this in css? I'd rather not have a specific animation per box. Because I may have hundred of boxes.
jsfiddle:
https://jsfiddle.net/foreyez/1n1en8uk/
This shows that at the point of animation, both boxes are at the same place... but if it were relative animation they would just be floating up and down in their current place.
Note: please don't use top/left to get relative position I'm looking specifically for relative TRANSFORMS. (if you must know why I'm doing this in 3d for the z axis as x,y are already used).
You can set position of .item elements to relative; use :nth-of-type() pseudo selector to set the top property of each element to the initial position where element should be animated from between the 20px range in relation to its .item sibling.
.item {
position: relative;
background: red;
animation: float 3s infinite ease-in-out;
width: 100px;
height: 100px;
}
.item:nth-of-type(1) {
top: 100px;
}
.item:nth-of-type(2) {
top: 300px;
}
#keyframes float {
from, to {
transform: translateY(-10px);
}
50% {
transform: translateY(10px);
}
}
<div class="item">
</div>
<div class="item">
</div>
jsfiddle https://jsfiddle.net/1n1en8uk/3/
Here's a nasty javascript solution (yes, I'm looking for a CSS solution that's why I won't mark this as the answer) that injects a dynamic keyframe to the page:
As long as there are less than 100 boxes should work alright I guess.
https://jsfiddle.net/foreyez/6v7n4ro3/
function getTranslateY(obj)
{
var transformMatrix = obj.css("-webkit-transform") ||
obj.css("-moz-transform") ||
obj.css("-ms-transform") ||
obj.css("-o-transform") ||
obj.css("transform");
var matrix = transformMatrix.replace(/[^0-9\-.,]/g, '').split(',');
var x = matrix[12] || matrix[4];//translate x
var y = matrix[13] || matrix[5];//translate y
return parseInt(y);
}
function insertAnimation(idx, yy)
{
var style1 = document.documentElement.appendChild(document.createElement("style")),
rule1 = "#keyframes float" + idx + " {\
from, to { transform: translateY("+ (yy-10) +"px); }\
50% { transform: translateY(" + (yy+10) + "px); }\
}";
style1.sheet.insertRule(rule1, 0);
}
$(".item").each(function(idx) {
var currentTranslateY = getTranslateY($(this));
insertAnimation(idx, currentTranslateY);
$(this).css('animation','float' + idx + ' 3s ease-in-out infinite');
});
Note: please don't use top/left to get relative position I'm looking
specifically for relative TRANSFORMS.
You can adjust html to single .item element as child of a container element with transform set to translateY(100px), position set to absolute; utilize css :after pseudo element at .item element with transform set to translateY(200px)
.float {
transform: translateY(100px);
position: absolute;
}
.item {
animation: float 3s infinite ease-in-out;
}
.item, .item:after {
display: block;
position: relative;
background: red;
width: 100px;
height: 100px;
}
.item:after {
content: "";
transform: translateY(200px);
}
#keyframes float {
from, to {
transform: translateY(-10px);
}
50% {
transform: translateY(10px);
}
}
<div class="float">
<div class="item">
</div>
</div>
jsfiddle https://jsfiddle.net/1n1en8uk/5/
I know how to run the animation from point A to point B, so to speak, but do not know how to run continuously in a circle. Below is a small code prepared:
.bg {
display: block;
position: absolute;
width: 100%;
height: 100%;
background: url(http://www.dejurka.ru/wp-content/uploads/2015/06/watercolor-patterns4.jpg) top left/30%;
animation: bg 2s cubic-bezier(0, -0.02, 1, 0.99);
}
#keyframes bg {
0% {
background-position: left -100px;
}
100% {
background-position: left 0px;
}
}
<div class="bg"></div>
help with script
If by:
run continuously in a circle
You mean run animation in a loop, then you can add the infinite animation-iteration-count property. In the shortcode you can just add infinite to your animation property. See demo:
.bg {
display: block;
position: absolute;
width: 100%;
height: 100%;
background: url(http://www.dejurka.ru/wp-content/uploads/2015/06/watercolor-patterns4.jpg) top left/30%;
animation: bg 2s cubic-bezier(0, -0.02, 1, 0.99) infinite;
}
#keyframes bg {
0% {
background-position: left -100px;
}
100% {
background-position: left 0px;
}
}
<div class="bg"></div>
Edit: jQuery Solution
With jQuery you can use the animate() function to achieve the same effect.
// bg animation function
function bgAnimate(speed) {
// define bg div
var el = $('.bg');
// get current background y-axis position and convert to integer (strips `px`)
var posY = parseInt(el.css("background-position-y"));
// animate element
el.animate({
'background-position-y': posY + 100 // add 100px
},{
duration: speed,
easing: 'linear',
complete: function() {
bgAnimate(speed); // repeat aniamtion (loop)
}
});
}
bgAnimate(1000); // call function
html, body {padding: 0;margin: 0;}
.bg {
display: block;
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
background-image: url(http://www.dejurka.ru/wp-content/uploads/2015/06/watercolor-patterns4.jpg);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="bg"></div>