My goal is to (upon clicking a button) spin a wheel multiple times, then end on a specific rotation value (0...360) while easing out the animation. I can currently 'smoothly' spin to a specific point on the wheel without any full revolutions. My problem is trying to spin the wheel multiple times and then landing on a specific point to make it look more realistic. Is this achievable with the CSS animaton property or anything else?
Here is my code...
const wheelEl = document.getElementById("wheel");
const sliceSize = 360 / 3;
function spinWheel(index) {
// Reset animation
wheelEl.style.transition = "none";
wheelEl.style.transform = "rotate(0deg)";
// Play animation on the next frame
setTimeout(() => {
wheelEl.style.transition = "all ease 1s";
// Target rotation margin
let rotMin = (sliceSize * (index))+(sliceSize/15);
let rotMax = (sliceSize * (index + 1))-(sliceSize/15);
// Target rotation
let rot = Math.floor(Math.random() * (rotMax - rotMin + 1) + rotMin);
wheelEl.style.transform = `rotate(-${rot}deg)`;
}, 0);
}
#container {
position: absolute;
text-align: center;
}
#arrow {
position: absolute;
top: -5px;
left: 50%;
transform: translateX(-50%);
border-top: 10px solid black;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
z-index: 1;
}
#wheel {
width: 100px;
height: 100px;
border-radius: 50%;
background-image: conic-gradient(lightpink 0 120deg, lightblue 0 240deg, lightsalmon 0 360deg);
}
<div id="container">
<div id="arrow"></div>
<div id="wheel"></div>
<br />
<button onclick="spinWheel(2)">Spin wheel</button>
</div>
You can stick to using transform.
This snippet does two things: adds a random (within bounds) number of 360 degrees to the rotation value and sets the easing function to ease-out so that the rotation slows down towards the end only.
So that the effect can be seen, the time of the full rotation is set to 5 seconds but of course alter this to whatever you want. You could for example make that random within some bounds too if desired.
You could also play with the Beziere function that represents ease-out if say you wanted a longer stretch of it seeming to slow down.
const wheelEl = document.getElementById("wheel");
const sliceSize = 360 / 3;
function spinWheel(index) {
// Reset animation
wheelEl.style.transition = "none";
wheelEl.style.transform = "rotate(0deg)";
// Play animation on the next frame
setTimeout(() => {
wheelEl.style.transition = "all ease-out 5s";
// Target rotation margin
const rotMin = (sliceSize * (index)) + (sliceSize / 15);
const rotMax = (sliceSize * (index + 1)) - (sliceSize / 15);
// Target rotation
const fullRots = Math.floor(Math.random() * 5) + 5; // minimum 5 rotations max 9
const rot = (fullRots * 360) + Math.floor(Math.random() * (rotMax - rotMin + 1) + rotMin);
wheelEl.style.transform = `rotate(-${rot}deg)`;
}, 0);
}
#container {
position: absolute;
text-align: center;
}
#arrow {
position: absolute;
top: -5px;
left: 50%;
transform: translateX(-50%);
border-top: 10px solid black;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
z-index: 1;
}
#wheel {
width: 100px;
height: 100px;
border-radius: 50%;
background-image: conic-gradient(lightpink 0 120deg, lightblue 0 240deg, lightsalmon 0 360deg);
}
<div id="container">
<div id="arrow"></div>
<div id="wheel"></div>
<br />
<button onclick="spinWheel(2)">Spin wheel</button>
</div>
Related
I'm working on my portfolio website so, I was trying some animation for the landing page. Everything is working fine but the animation takes very long time to load. I was generating a matrix of divs using JS and appling CSS and animation to each one of them. Please suggest something to decrease the load time.
My Website
Javascript
let holder = document.getElementById("hero");
let gs = 25;
window.onload = () => {
for (let i = 0; i < gs; i++) {
for (let j = 0; j < gs; j++) {
let dot = document.createElement("div");
dot.classList.add("dot");
dot.style.animationDelay = `${Math.sin(i * j) / 2}s`;
holder.appendChild(dot);
}
}
};
CSS
.hero {
/* background-image: url(media/Background.jpg); */
height: 100vh;
position: absolute;
width: 100%;
display: grid;
grid-template-columns: repeat(50, 90px);
grid-template-rows: repeat(50, 90px);
overflow: hidden;
text-align: left;
}
.dot {
width: 2px;
height: 2px;
animation-name: wavyDots;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-direction: alternate-reverse;
z-index: -1;
}
.dot:nth-child(odd) {
background-color: #0cf5d5;
}
.dot:nth-child(even) {
background-color: #e0017a;
}
#keyframes wavyDots {
0% {
transform: translate(0, 0);
}
100% {
transform: translate(30px, 30px);
}
}
I tried decreasing the matrix size but didn't help much, if I decrease more then I don't have enough divs to cover my entire page.
I have a div with a 2 pixel dashed boarder, and I would like to animate it so that it fills in with a different color over time until it is all filled in, and then would repeat over and over again.
I have a jsfiddle with the style that I have around my div
.widget{
height: 50px;
margin: 0px 20px 0px 0px;
display: flex;
justify-content: center;
flex-direction: column;
padding: 10px;
border: 2px dashed #E4E6EF;
border-radius: 5px;
position: relative;
}
<div class="widget">
test
</div>
https://jsfiddle.net/kqfd5jh9/
The height is fixed but the width isn't, it would depend on how many I have.
I am not to sure where to start or what method would be best to achieve this, does anyone have any ideas?
I attached a snippet that fulfills your requirements, border color will change infinitely every 10 seconds to green color. Just added an animation property on widget and the keyframes setup the initial color and ending color. Hope it helps.
For more information about CSS animations refers here.
Edit
As you requested, I made a working snippet that does what you want, I had to use an svg and javascript , to make it work. Hope it helps, the code is very straight forward.
You can see this for further research of how it works:
stroke-dasharray
stroke-dashoffset
window.onload = () => {
const border = document.querySelector('.widget rect#color');
const borderLength = border.getTotalLength();
const timeFactor = 4; // Highest number is fastest
let borderOffset = borderLength;
border.style.strokeDashoffset = borderLength;
border.style.strokeDasharray = borderLength / 2;
let initialTime;
let isColorAnimationSetted = false;
const animation = () => {
// Set the frame time only first time
if (!initialTime) {
initialTime = new Date();
} else {
!isColorAnimationSetted && setColorAnimation();
}
borderOffset -= timeFactor;
border.style.strokeDashoffset = borderOffset;
window.requestAnimationFrame(animation);
};
// We set the time for the color animation to match the progress animation to be completed
const setColorAnimation = () => {
const frameTime = new Date() - initialTime;
const framesToEnd = (2 * borderOffset) / timeFactor;
const timeToComplete = framesToEnd * frameTime;
border.style.animation = `color-change ${timeToComplete}ms linear infinite`;
isColorAnimationSetted = true;
};
window.requestAnimationFrame(animation);
};
.widget{
height: 50px;
margin: 0px 20px 0px 0px;
display: flex;
justify-content: center;
flex-direction: column;
padding: 10px;
position: relative;
}
.widget svg {
height: 100%;
width: 100%;
}
rect#color {
stroke: transparent;
stroke-width: 5;
fill: none;
border-radius: 5px;
}
rect#dash {
stroke: white;
stroke-width: 6;
fill: none;
stroke-dasharray: 5,5;
border-radius: 5px;
}
p {
position: absolute;
left: 40px;
}
#keyframes color-change {
0% {
stroke: red; /* Starting color */
}
50% {
stroke: #00FF00; /* Ending color */
}
100% {
stroke: red; /* Starting color */
}
}
<div class="widget">
<svg width="100%" height="100%" preserveAspectRatio='none'>
<rect id="color" x="0" y="0" height="50px" width="100%" />
<rect id="dash" x="0" y="0" height="50px" width="100%" />
</svg>
<p>Test<p>
</div>
I found a button on a website that has the animation of a google Button.
How do you make such a button that it makes an animation wherever you click?
Here is my code what I have done so far:
button {
text-transform: uppercase;
padding: 0.8em;
width: 100px;
background: #0053d9;
color: #fff;
border: none;
border-radius: 5px;
transition: all 0.2s;
font-size: 15px;
font-weight: 500;
}
button:hover {
filter: brightness(80%);
cursor: pointer;
}
button:active {
transform: scale(0.92)
}
<button>Login</button>
This effect is known as the Material ripple effect (or at least that's along the lines of what most people call it).
There are two ways to accomplish this effect - one using JS and CSS, for the full-fledged effect, which means the ripple comes out of where the mouse is, and one using pure CSS, and no JS - which results in the ripple coming out of the button no matter where the mouse is inside the button.
Some people prefer the CSS-only one as it is cleaner, but most prefer the full-fledged version as it takes into account the mouse position and hence delivers a slightly better experience...
Anyway, I've created both these effects, chose whichever you prefer :).
PS: here are the rules for any full-fledged versions you see:
The ripple must be created when the mouse is down on the button - not when the mouse is clicked because that takes an extra hundred miliseconds on mobile devices (because mobile browsers delay delivering the click event to be able to check if it is a single click or a double click). So with this kind of dalay before showing the ripple, user experience goes down drastically as your site will seem slow and laggy even though it probably isn't.
The ripple must stay on the button and cover its background until the mouse is up, or the button has lost focus - whichever comes first.
Without further ado, here is the code...
window.addEventListener("mousedown", e => {
const target = e.target;
if(target.nodeName == "BUTTON" && !target.classList.contains("css-only-ripple")) {
show_ripple(target);
}
});
function show_ripple(button) {
const style = getComputedStyle(button);
let ripple_elmnt = document.createElement("span");
let diameter = Math.max(parseInt(style.height), parseInt(style.width)) * 1.5;
let radius = diameter / 2;
ripple_elmnt.className = "ripple";
ripple_elmnt.style.height = ripple_elmnt.style.width = diameter + "px";
ripple_elmnt.style.position = "absolute";
ripple_elmnt.style.borderRadius = "1000px";
ripple_elmnt.style.pointerEvents = "none";
ripple_elmnt.style.left = event.clientX - button.offsetLeft - radius + "px";
ripple_elmnt.style.top = event.clientY - button.offsetTop - radius + "px";
ripple_elmnt.style.transform = "scale(0)";
ripple_elmnt.style.transition = "transform 500ms ease, opacity 400ms ease";
ripple_elmnt.style.background = "rgba(255,255,255,0.5)";
button.appendChild(ripple_elmnt);
setTimeout(() => {
ripple_elmnt.style.transform = "scale(1)";
}, 10);
button.addEventListener("mouseup", e => {
ripple_elmnt.style.opacity = 0;
setTimeout(() => {
try {
button.removeChild(ripple_elmnt);
} catch(er) {}
}, 400);
}, {once: true});
button.addEventListener("blur", e => {
ripple_elmnt.style.opacity = 0;
setTimeout(() => {
try {
button.removeChild(ripple_elmnt);
} catch(er) {}
}, 450);
}, {once: true});
}
button {
text-transform: uppercase;
padding: 0.8em;
width: 100px;
background: #0053d9;
color: #fff;
border: none;
border-radius: 5px;
transition: all 0.2s;
font-size: 15px;
font-weight: 500;
position: relative;
overflow: hidden;
}
button:hover {
filter: brightness(80%);
cursor: pointer;
}
button:active {
transform: scale(0.92)
}
.css-only-ripple::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 150%;
aspect-ratio: 1 / 1;
transform: translate(-50%, -50%) scale(0);
pointer-events: none;
border-radius: 999px;
background: rgba(255, 255, 255, .5);
}
.css-only-ripple:focus::after {
animation: scale_up 1000ms forwards;
}
#keyframes scale_up {
0% {
transform: translate(-50%, -50%) scale(0);
opacity: 1;
}
100% {
transform: translate(-50%, -50%) scale(1);
opacity: 0;
}
}
<button>Login</button>
<button class="css-only-ripple">Login</button>
<br>
The first button is the CSS and JS version, and the second is the CSS-only version. For the CSS-only button, you have to unfocus it before you click it again or the ripple will not show (it only gets created on focus)
I really like this element,
but how to create it? I am not sure what's the correct designation of the element...
Thank you very much.
This effect can be achieved by layering a couple arc()s:
// bright blue full circle
d.beginPath();
d.arc(50, 50, 50, 0, 2 * Math.PI, false);
d.fillStyle = "#aaeeff";
d.fill();
// dark blue percentage circle
d.beginPath();
d.moveTo(50, 50);
d.arc(50, 50, 50, -0.5 * Math.PI, 0.78 * 2 * Math.PI - 0.5 * Math.PI, false);
d.fillStyle = "#00aaff";
d.fill();
// white inner filler
d.beginPath();
d.moveTo(50, 50);
d.arc(50, 50, 25, 0, 2 * Math.PI, false);
d.fillStyle = "#ffffff";
d.fill();
and finally rendering the text:
d.moveTo(50, 50);
d.fillStyle = "#606060";
d.font = "12pt sans-serif";
d.fillText("78%", 36, 56);
Fiddle: http://jsfiddle.net/j6NVg/
Instead of using the <canvas> element, I have chosen to construct the pie chart relying on CSS and JS entirely. The HTML markup is as follow:
<div class="pie" data-percent="78">
<div class="left">
<span></span>
</div>
<div class="right">
<span></span>
</div>
</div>
The CSS is as follow. The trick is to split the circle into two halves (the nested .left and .right elements). The halves will have their overflowing content hidden, and contain nested <span> that we will manipulate with JS for rotation later. Add vendor prefixes when appropriate :)
.pie {
background-color: #eee;
border-radius: 50%;
width: 200px;
height: 200px;
overflow: hidden;
position: relative;
}
.pie > div {
float: left;
width: 50%;
height: 100%;
position: relative;
overflow: hidden;
}
.pie span {
background-color: #4a7298;
display: block;
width: 100%;
height: 100%;
}
.pie .left span {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
-webkit-transform-origin: 100% 50%;
transform-origin: 100% 50%;
}
.pie .right span {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
-webkit-transform-origin: 0% 50%;
transform-origin: 0% 50%;
}
.pie:before,
.pie:after {
border-radius: 50%;
display: block;
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translateX(-50%) translateY(-50%);
transform: translateX(-50%) translateY(-50%);
}
.pie:before {
background-color: #fff;
content: "";
width: 75%;
height: 75%;
z-index: 100;
}
.pie:after {
content: attr(data-percent) "%";
z-index: 200;
text-align: center;
}
I have used the following with jQuery:
$(function() {
$(".pie").each(function() {
var percent = $(this).data("percent").slice(0,-1), // Removes '%'
$left = $(this).find(".left span"),
$right = $(this).find(".right span"),
deg;
if(percent<=50) {
// Hide left
$left.hide();
// Adjust right
deg = 180 - (percent/100*360)
$right.css({
"transform": "rotateZ(-"+deg+"deg)"
});
} else {
// Adjust left
deg = 180 - ((percent-50)/100*360)
$left.css({
"transform": "rotateZ(-"+deg+"deg)"
});
}
});
});
Here is the fiddle: http://jsfiddle.net/Aw5Rf/7/
Check the below links for more info (not an exact one.But you can get some idea).
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Canvas Test</title>
</head>
<body>
<section>
<div>
<canvas id="canvas" width="400" height="300">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>
<script type="text/javascript">
var myColor = ["#ECD078","#D95B43","#C02942","#542437","#53777A"];
var myData = [10,30,20,60,40];
function getTotal(){
var myTotal = 0;
for (var j = 0; j < myData.length; j++) {
myTotal += (typeof myData[j] == 'number') ? myData[j] : 0;
}
return myTotal;
}
function plotData() {
var canvas;
var ctx;
var lastend = 0;
var myTotal = getTotal();
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < myData.length; i++) {
ctx.fillStyle = myColor[i];
ctx.beginPath();
ctx.moveTo(200,150);
ctx.arc(200,150,150,lastend,lastend+
(Math.PI*2*(myData[i]/myTotal)),false);
ctx.lineTo(200,150);
ctx.fill();
lastend += Math.PI*2*(myData[i]/myTotal);
}
}
plotData();
</script>
</section>
</body>
</html>
For more info :Graphing Data in the HTML5 Canvas Element Simple Pie Charts
Another Link : Pure CSS3 Pie Charts effect
This is an online demo: http://jsbin.com/uFaSOwO/1/
First of all what you need can be done exactly using jQuery knob plugin. Still interested in a CSS Solution, than here's what I have done
<div class="load_me"></div>
.load_me {
margin: 100px;
height: 50px;
width: 50px;
border: 5px solid #f00;
border-radius: 50%;
border-top-color: transparent;
}
Demo
Animating the Knob Credits
If you want to prevent the mouse alteration, you can simply add readOnly
$this.knob({
readOnly: true
});
Demo
FIDDLE with ANIMATION
Here's my approach:
var ctx = canvas.getContext('2d');
/*
* in canvas, 0 degrees angle is on the right edge of a circle,
* while we want to start at the top edge of the circle.
* We'll use this variable to compensate the difference.
*/
var relativeAngle = 270;
function drawCanvas() {
ctx.clearRect(0, 0, 90, 90);
//light blue circle
ctx.lineWidth = 20;
ctx.strokeStyle = '#D8E8F7';
ctx.beginPath();
ctx.arc(45, 45, 35, 0, 2*Math.PI);
ctx.stroke();
//dark blue circle
ctx.strokeStyle = '#66ADF4';
ctx.beginPath();
//notice the angle conversion from degrees to radians in the 5th argument
ctx.arc(45, 45, 35, 1.5*Math.PI, ((angle + relativeAngle) / 180) * Math.PI);
ctx.stroke();
//text
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillStyle = '#666';
ctx.font = 'bold 14px serif';
// angle conversion to percentage value
ctx.fillText(parseInt(100 * angle / 360).toString() + '%', 45, 45);
}
var angle;
function timeout() {
angle = parseInt(360 * percent / 100);
drawCanvas();
if (angle > 360) {
document.getElementById('run').disabled = false;
percent = 0;
return;
}
percent++;
setTimeout(timeout, 10);
};
var percent = 0;
/* START the ANIMATION */
timeout();
At the bottom of the code you'll find a self evaluating function timeout which calls the drawCanvas function every 10 miliseconds and increments the blue circle angle. I hope everything is clear here. If not, feel free to ask!
Enjoy it!
I know how to create a notch on the outside like:
div:after {
content: '';
display: block;
width: 20px;
height: 20px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
But I can't figure out how to solve this thingy using CSS only:
The notch has to be inside of the container and it has to be transparent. So the above solution or an image won't solve it.
Maybe this can be created using SVG?
Edit
What I tried is this:
body {
background: #eee;
}
div {
position: relative;
height: 100px;
width: 200px;
background: #ccc;
}
div:after {
content: '';
position: absolute;
display: block;
top: 40px;
right: -10px;
width: 20px;
height: 20px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
background: #eee;
}
But this is clearly no soultion, because the pseudo element is not tranparent.
You cannot do this with pure CSS as clipping is not fully supported yet in all browsers (if cross-compatibility is important).
You would need to combine SVG clipping paths with CSS clipping and would end up with a not so elegant solution.
What you can do however is to create a background image using canvas. Canvas is supported in all the major HTML5 capable browsers. The backdraw with canvas is that you need to do a little more coding to create the shape. Optional an image could have been used instead but using canvas allow you to keep everything sharp (and not blurry as with an image when it is stretched).
The following solution will produce this result (I added red border to show the transparent region). You can tweak the parameters to get it look exactly as you need it to look and extend it with arguments to define size of the notch, width of transparent area etc. The code automatically adopts to the size of the element given as argument.
To add a notch simply call:
addNotch(element);
ONLINE DEMO HERE
The code is straight-forward and performs fast:
function addNotch(element) {
/// some setup
var canvas = document.createElement('canvas'),
ctx = canvas.getContext('2d'),
/// get size of element in pixels
cs = window.getComputedStyle(element),
w = parseInt(cs.getPropertyValue('width') || '0', 10),
h = parseInt(cs.getPropertyValue('height') || '0', 10),
/// pre-calculate some values
hh = h * 0.5,
nw = 20, /// notch size
nh = nw * 0.5;
canvas.width = w;
canvas.height = h;
/// draw the main shape
ctx.moveTo(0, 0);
ctx.lineTo(w - nw, 0);
ctx.lineTo(w - nw, hh - nh);
ctx.lineTo(w - nw - nh, hh);
ctx.lineTo(w - nw, hh + nh);
ctx.lineTo(w - nw, h);
ctx.lineTo(0, h);
ctx.closePath();
ctx.fillStyle = '#7c7058';
ctx.fill();
/// draw the white arrow
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = '#eee';
ctx.moveTo(w - nw - nw * 0.33, hh - nw * 0.75);
ctx.lineTo(w - nw - nw * 1.1, hh);
ctx.lineTo(w - nw - nw * 0.33, hh + nw * 0.75);
ctx.stroke();
/// convert canvas to image and set background of element
/// with this image
element.style.background = 'url(' + canvas.toDataURL() +
') no-repeat left top';
}
Here's an example using SVG clipping.
jsFiddle Demo
<div></div>
<svg>
<defs>
<clipPath id="clipping">
<polygon points="
0 0, 202 0,
202 36, 185 50, 202 64,
202 102, 0 102" />
</clipPath>
</defs>
</svg>
Try this fiddle out, it should set you on your way for what you're looking for.
#notched {
width: 0px;
height: 0px;
border-right: 60px solid transparent;
border-top: 60px solid #d35400;
border-left: 60px solid #d35400;
border-bottom: 60px solid #d35400;
}
Updated fiddle
You can use the :before for mask and after selector for the border, just set border-lef and border-bottom property.
div:before {
content: '';
position: absolute;
display: block;
top: 40px;
right: -10px;
width: 20px;
height: 20px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
background: #eee;
}
div:after {
content: '';
position: absolute;
display: block;
top: 38px;
right: -5px;
width: 20px;
height: 21px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
background: transparent;
border-left: 2px solid #eee;
border-bottom: 2px solid #eee;
}
the result:
jsFiddle