I am trying to draw something like shown below with Javascript and wondering how I can accomplish this.
Obviously to draw a circle, I can do:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>shapes demo</title>
</head>
<body>
<canvas class="canvas" width="400" height="200"></canvas><br />
<button class="btn">draw</button>
<script>
let canvas = document.querySelector(".canvas");
let btn = document.querySelector(".btn");
btn.addEventListener("click", () => {
var circle = canvas.getContext("2d");
circle.beginPath();
circle.arc(180, 100, 90, 0, 2 * Math.PI);
circle.stroke();
});
</script>
</body>
</html>
I am wondering maybe there is some existing drawing library, etc. which would make this easier. Any advice would be helpful. Thanks!
*I want to write some code for visualizing a few data structures. If you are wondering why anyone would want to do this đ
Edit
I think actually, just having a way to draw a curved arrow joining two strings is enough. It is easy to draw a partial circle, but I am not sure how to add text at the end of the partial circle or add an arrow tip to the end of the partial circle.
First, I separated the circle into 4 different arcs. Second, I added the numbers. And third, using some trig, added an âarrowâ function to create an arrow at a certain angle around the circle.
And hereâs the finished product:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>shapes demo</title>
</head>
<body>
<canvas class="canvas" width="400" height="200"></canvas><br />
<button class="btn">draw</button>
<script>
function arrow (ang) {
circle.save();
circle.translate(180+70*Math.cos(ang), 100+70*Math.sin(ang));
circle.rotate(ang);
circle.beginPath();
circle.moveTo(-5, -5);
circle.lineTo(0, 0);
circle.lineTo(5, -5);
circle.stroke();
circle.restore();
}
let canvas = document.querySelector(".canvas");
var circle = canvas.getContext("2d");
let btn = document.querySelector(".btn");
btn.addEventListener("click", () => {
circle.beginPath();
circle.arc(180, 100, 70, Math.PI*7/12, Math.PI*11/12); // bottom left
circle.stroke();
circle.beginPath();
circle.arc(180, 100, 70, Math.PI*13/12, Math.PI*17/12); // top left
circle.stroke();
circle.beginPath();
circle.arc(180, 100, 70, Math.PI*19/12, Math.PI*23/12); // top right
circle.stroke();
circle.beginPath();
circle.arc(180, 100, 70, Math.PI*1/12, Math.PI*5/12); // bottom right
circle.stroke();
circle.beginPath();
circle.textAlign = 'center';
circle.textBaseline = 'middle';
circle.font = '20px Arial';
circle.fillText('1', 180, 30);
circle.fillText('2', 250, 100);
circle.fillText('3', 180, 170);
circle.fillText('4', 110, 100);
arrow(Math.PI*11/12);
arrow(Math.PI*17/12);
arrow(Math.PI*23/12);
arrow(Math.PI*5/12);
});
</script>
</body>
</html>
Interested in CSS arrows? ...you just need to add some more modifiers to position/rotate them well to your needs - I did three of them ready for you - all pointing the position which you state in style="left:0px;top:0px" next to the html tags:
.content {
position:relative;
width: 100%;
height: 800px;
border:1px solid red;
}
/* modifiers */
.arrow.left-from-up {
margin: -160px 0 0 120px;
transform: rotate(90deg);
}
.arrow.left-from-bottom {
margin: -160px 0 0 120px;
transform: rotate(90deg) scaleX(-1);
}
.arrow.up-from-left {
transform: rotate(180deg);
margin: 0 0 0 -20px;
}
/* Arrow */
.arrow {
position: absolute;
transform-origin: 50% 50%;
margin: 0;
width: 100px;
pointer-events:none;
}
.arrow .curve {
border: 2px solid #BE5F4B;
border-color: transparent transparent transparent #BE5F4B;
height: 360px;
width: 1200px;
border-radius: 230px 0 0 150px;
}
.arrow .point {
position: absolute;
left: 40px;
top: 315px;
}
.arrow .point:before, .arrow .point:after {
border: 1px solid #BE5F4B;
height: 25px;
content: "";
position: absolute;
}
.arrow .point:before {
top: -11px;
left: -11px;
transform:rotate(-74deg);
-webkit-transform:rotate(-74deg);
-moz-transform:rotate(-74deg);
-ms-transform: rotate(-74deg);
}
.arrow .point:after {
top: -20px;
left: 5px;
transform:rotate(12deg);
-webkit-transform: rotate(12deg);
-moz-transform:rotate(12deg);
-ms-transform: rotate(12deg);
}
<div class="content">
<div class="arrow left-from-up" style="left:0;top:50px;">
<div class="curve"></div>
<div class="point"></div>
</div>
<div class="arrow left-from-bottom" style="left:0;top:90px">
<div class="curve"></div>
<div class="point"></div>
</div>
<div class="arrow up-from-left" style="left:0;top:110px">
<div class="curve"></div>
<div class="point"></div>
</div>
</div>
original code found here: https://codepen.io/zomgbre/pen/kmCsp
Related
How do I get my canvas animation of the rain to start when the play button is clicked and vice versa (stop the rain when the pause button is clicked)?
I really would appreciate a helping hand.
So far I have the rain animation to play as soon as the window is loaded and the audio in the background is the only thing that plays when interacting with the play button.
I'm not sure how to go about getting the rain animation to start so that the audio also plays in sync with the rain and vice versa.
// Get the canvas and context and store in variables
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Set canvas dimensions to window height and width
canvas.width = window.innerHeight;
canvas.height = window.innerHeight;
// Generate the raindrops and apply attributes
var rainNum = 200; // max raindrops
var rainDrops = [];
let isClicked = true;
// Loop through the empty raindrops and apply attributes
for (var i = 0; i < rainNum; i++) {
rainDrops.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
})
}
// Draw raindrops onto canvas
function draw() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.lineWidth = 0.1;
context.strokeStyle = "white";
context.beginPath();
for (var i = 0; i < rainNum; i++) {
var r = rainDrops[i];
context.moveTo(r.x, r.y);
context.lineTo(r.x, r.y + 30);
rainDrops[i].y += 13;
context.stroke();
}
if (isClicked == true) {
moveRain();
} else {
return false
}
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
// Animate the raindrops
function moveRain() {
for (var i = 0; i < rainNum; i++) {
// Store current raindrops
var r = rainDrops[i];
// If the rain reaches the bottom, send a new one to the top
if (r.y > canvas.height) {
rainDrops[i] = {
x: Math.random() * canvas.width,
y: 0
};
}
}
}
// Create a reference to the audio
var audioOne = document.querySelector("#audio-1");
function playAudio() {
if (isClicked == true) {
isClicked = false
audioOne.pause();
btn.className = "play";
} else if (isClicked == false) {
isClicked = true
audioOne.play();
btn.className = "pause";
draw()
}
}
html {
height: 100%;
width: 100%;
}
body {
height: 100vh;
width: 100vw;
margin: 0;
padding: 0;
overflow: hidden;
}
canvas {
height: 100%;
width: 100%;
background-color: transparent;
position: absolute;
z-index: 10;
}
#sky-top {
height: 100%;
width: 100%;
position: absolute;
z-index: 1;
animation: lightning 20s ease-in-out infinite;
}
#keyframes lightning {
/****** This will create a lightning effect every 20 seconds ******/
0% {
background-color: rgb(46, 46, 46);
}
6.25% {
background-color: rgb(46, 46, 46);
}
8% {
background-color: rgb(255, 255, 255);
}
9% {
background-color: rgb(46, 46, 46);
}
11% {
background-color: rgb(255, 255, 255);
}
30% {
background-color: rgb(46, 46, 46);
}
100% {
background-color: rgb(46, 46, 46);
}
}
#sky-bottom {
height: 100%;
width: 100%;
position: absolute;
z-index: 2;
background: linear-gradient(rgba(255, 255, 255, 0), rgb(45, 45, 45));
}
.center-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
z-index: 20;
background-color: transparent;
}
.button-center {
position: absolute;
top: 40%;
-webkit-transform: translateY(-50%);
-moz-transform: translateY(-50%);
-ms-transform: translateY(-50%);
-o-transform: translateY(-50%);
transform: translateY(-50%);
-webkit-transform: translateX(-50%);
-moz-transform: translateX(-50%);
-ms-transform: translateX(-50%);
-o-transform: translateX(-50%);
transform: translateX(-50%);
}
.center-container:after,
.button-center {
display: inline-block;
vertical-align: middle;
}
#btn {
height: 130px;
width: 130px;
border: none;
background-size: 100% 100%;
outline: none;
}
.play {
background: url('../image/play-button.png');
border-radius: 50%;
cursor: pointer;
-webkit-filter: drop-shadow(2px 2px 2px #666666);
filter: drop-shadow(2px 2px 2px #666666);
}
.pause {
background: url('../image/pause-button.png');
border-radius: 50%;
cursor: pointer;
-webkit-filter: drop-shadow(2px 2px 2px #666666);
filter: drop-shadow(2px 2px 2px #666666);
}
<!DOCTYPE html>
<html lang="en">
<head>
<title>Rain</title>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Relax your mind with some rain and thunder.">
<link href="css/styles.css" type="text/css" rel="stylesheet">
</head>
<body>
<div id="sky-top"></div>
<div id="sky-bottom"></div>
<canvas id="canvas"></canvas>
<div class="center-container">
<div class="button-center">
<button id="btn" class="play" class="pause" onclick="playAudio()"></button>
</div>
<audio src="audio/rain-and-thunder.mp3" id="audio-1" loop="loop" type="audio/mp3"></audio>
</div>
<script src="js/script.js"></script>
</body>
</html>
First off, the audio file won't work for me here locally (I downloaded my own mp3 - I am working on getting it to work now), so I focused on playing/pausing the canvas animation and ignored the sound functionality. Let's start with the HTML:
<html>
<head>
<link rel = "stylesheet"
type = "text/css"
href = "index.css" />
</head>
<body>
<canvas id="canvas"></canvas>
<div class="center-container">
<div class="button-center">
<button id="btn" class="play" class="pause" onclick="playAudio()"></button>
</div>
<audio loop="loop" id="audio-1" width="100%" height="auto">
<source src="audio/rain-and-thunder.mp3" type="audio/mpeg">
</audio>
</div>
</body>
<script src='index.js'></script>
</html>
You'll see I did a few things to display the canvas properly. For whatever reason, I couldn't see your button and the canvas was showing up blank. I decided to inline CSS the width and height, as well as put the script within the body.
Also, since we are ignoring the sound functionality, I renamed the function to startStopRain() to make more sense in your JS file. Let's talk about that next:
// Get the canvas and context and store in variables
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var audioOne = document.getElementById("audio-1");
// Set canvas dimensions to window height and width
canvas.width = window.innerHeight;
canvas.height = window.innerHeight;
// Generate the raindrops and apply attributes
var rainNum = 200; // max raindrops
var rainDrops = [];
let isClicked = false
// Loop through the empty raindrops and apply attributes
for(var i = 0; i < rainNum; i ++)
{
rainDrops.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
})
}
// Draw raindrops onto canvas
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.lineWidth = 0.1;
context.strokeStyle = "red";
context.beginPath();
for(var i = 0; i < rainNum; i ++)
{
var r = rainDrops[i];
context.moveTo(r.x, r.y);
context.lineTo(r.x, r.y + 30);
rainDrops[i].y += 13;
context.stroke();
}
if (isClicked == true) {
moveRain();
} else{
return false
}
document.getElementById("canvas").onclick = moveRain();
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
// Animate the raindrops
function moveRain(){
for(var i = 0; i < rainNum; i++)
{
// Store current raindrops
var r = rainDrops[i];
// If the rain reaches the bottom, send a new one to the top
if(r.y > canvas.height) {
rainDrops[i] = {x: Math.random() * canvas.width, y: 0};
}
}
}
// Create a reference to the audio
// need to figure out why the audio wont play - this is new to me :D
function playAudio(){
audioOne.play();
if (isClicked == true){
isClicked = false
audioOne.pause();
} else if (isClicked == false){
isClicked = true
audioOne.play();
draw()
}
}
Your original intent was to use two class names on the btn so you could distinguish between play/pause. While there are use cases for using multiple class names, I don't personally believe this is one of them. Instead, I used a global isClicked variable in the script so I could control the flow of the functions.
Follow the isClicked variable from top to bottom to better understand how I used it to call/stop functions. Minus initializing the variable at the top, you'll notice it shows up in only two functions: draw() and startStopRain().
EDIT: The flow control for the sound should be no different. You can use the same isClicked boolean value to determine when the sound should be played or not. If you want, I can update it to reflect that but truthfully, that would be good practice for you. Also, I changed the id of the button to audio-1 since the original code was selecting the element via that specific id
I am making a vanilla js carousel. I have laid out basic previous and next functionality using js along with html and css.
Now I tried to use css-animations (keyframes) to do left and right slide-in/slide-out animations but the code became messy for me. So here I am asking that what minimal changes would be needed to get the same animation effects in this implementation ?
Will you go for pure JS based or pure CSS based or a mix to do the same ?
My goal is get proper animation with minimal code.
(function () {
let visibleIndex = 0;
let carousalImages = document.querySelectorAll(".carousal__image");
let totalImages = [...carousalImages].length;
function makeNextVisible() {
visibleIndex++;
if (visibleIndex > totalImages - 1) {
visibleIndex = 0;
}
resetVisible();
renderVisible();
}
function makePrevVisible() {
visibleIndex--;
if (visibleIndex < 0) {
visibleIndex = totalImages - 1;
}
resetVisible();
renderVisible();
}
function resetVisible() {
for (let index = 0; index < totalImages; index++) {
carousalImages[index].className = "carousal__image";
}
}
function renderVisible() {
carousalImages[visibleIndex].className = "carousal__image--visible";
}
function renderCarousel({ autoplay = false, autoplayTime = 1000 } = {}) {
if (autoplay) {
[...document.querySelectorAll("button")].forEach(
(btn) => (btn.style.display = "none")
);
setInterval(() => {
makeNextVisible();
}, autoplayTime);
} else renderVisible();
}
renderCarousel();
// Add {autoplay:true} as argument to above to autplay the carousel.
this.makeNextVisible = makeNextVisible;
this.makePrevVisible = makePrevVisible;
})();
.carousal {
display: flex;
align-items: center;
}
.carousal__wrapper {
width: 500px;
height: 400px;
}
.carousal__images {
display: flex;
overflow: hidden;
list-style-type: none;
padding: 0;
}
.carousal__image--visible {
position: relative;
}
.carousal__image {
display: none;
}
<div class='carousal'>
<div class='carousal__left'>
<button onclick='makePrevVisible()'>Left</button>
</div>
<section class='carousal__wrapper'>
<ul class='carousal__images'>
<li class='carousal__image'>
<img src='https://fastly.syfy.com/sites/syfy/files/styles/1200x680/public/2018/03/dragon-ball-super-goku-ultra-instinct-mastered-01.jpg?offset-x=0&offset-y=0' alt='UI Goku' / width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://www.theburnin.com/wp-content/uploads/2019/01/super-broly-3.png' alt='Broly Legendary' width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://lh3.googleusercontent.com/proxy/xjEVDYoZy8-CTtPZGsQCq2PW7I-1YM5_S5GPrAdlYL2i4SBoZC-zgtg2r3MqH85BubDZuR3AAW4Gp6Ue-B-T2Z1FkKW99SPHwAce5Q_unUpwtm4' alt='Vegeta Base' width='500' height='400'/>
</li>
<li class='carousal__image'>
<img src='https://am21.mediaite.com/tms/cnt/uploads/2018/09/GohanSS2.jpg' alt='Gohan SS2' width='500' height='400'/>
</li>
</ul>
</section>
<div class='carousal__right'>
<button onclick='makeNextVisible()'>Right</button>
</div>
</div>
Updated codepen with feedback from the below answers and minor additional functionalities = https://codepen.io/lapstjup/pen/RwoRWVe
I think the trick is pretty simple. ;)
You should not move one or two images at the same time. Instead you should move ALL images at once.
Let's start with the CSS:
.carousal {
position: relative;
display: block;
}
.carousal__wrapper {
width: 500px;
height: 400px;
position: relative;
display: block;
overflow: hidden;
margin: 0;
padding: 0;
}
.carousal__wrapper,
.carousal__images {
transform: translate3d(0, 0, 0);
}
.carousal__images {
position: relative;
top: 0;
left: 0;
display: block;
margin-left: auto;
margin-right: auto;
}
.carousal__image {
float: left;
height: 100%;
min-height: 1px;
}
2nd step would be to calculate the maximum width for .carousal__images. For example in your case 4 * 500px makes 2000px. This value must be added to your carousal__images as part of the style attribute style="width: 2000px".
3rd step would be to calculate the next animation point and using transform: translate3d. We start at 0 and want the next slide which means that we have slide to the left. We also know the width of one slide. So the result would be -500px which also has to be added the style attribute of carousal__images => style="width: 2000px; transform: translate3d(-500px, 0px, 0px);"
That's it.
Link to my CodePen: Codepen for Basic Carousel with Autoplay
Try this. First stack all the images next to each other in a div and only show a single image at a time by setting overflow property to hidden for the div. Next, add event listeners to the buttons. When a bottom is clicked, the div containing the images is translated by -{size of an image} * {image number} on the x axis. For smooth animation, add transition: all 0.5s ease-in-out; to the div.
When someone clicks left arrow on the first image, the slide should display the last image. So for that counter is set to {number of images} - 1 and image is translated to left size * counter px.
For every click on the right arrow, the counter is incremented by 1 and slide is moved left. For every click on the left arrow, the counter is decremented by 1.
Slide.style.transform = "translateX(" + (-size * counter) + "px)"; this is the condition which is deciding how much the slide should be translated.
const PreviousButton = document.querySelector(".Previous-Button");
const NextButton = document.querySelector(".Next-Button");
const Images = document.querySelectorAll("img");
const Slide = document.querySelector(".Images");
const size = Slide.clientWidth;
var counter = 0;
// Arrow Click Events
PreviousButton.addEventListener("click", Previous);
NextButton.addEventListener("click", Next);
function Previous() {
counter--;
if (counter < 0) {
counter = Images.length - 1;
}
Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
function Next() {
counter++;
if (counter >= Images.length) {
counter = 0;
}
Slide.style.transform = "translateX(" + (-size * counter) + "px)";
}
* {
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
.Container {
width: 60%;
margin: 0px auto;
margin-top: 90px;
overflow: hidden;
position: relative;
}
.Container .Images img {
width: 100%;
}
.Images {
transition: all 0.5s ease-in-out;
}
.Container .Previous-Button {
position: absolute;
background: transparent;
border: 0px;
outline: 0px;
top: 50%;
left: 20px;
transform: translateY(-50%);
filter: invert(80%);
z-index: 1;
}
.Container .Next-Button {
position: absolute;
background: transparent;
border: 0px;
outline: 0px;
top: 50%;
right: 20px;
transform: translateY(-50%);
filter: invert(80%);
z-index: 1;
}
.Container .Images {
display: flex;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://fonts.googleapis.com/css2?family=Cabin&family=Poppins&display=swap" rel="stylesheet">
<link rel="stylesheet" href="style.css">
<title>Carousel</title>
</head>
<body>
<div class="Container">
<button class="Previous-Button">
<svg style = "transform: rotate(180deg);" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
</button>
<button class="Next-Button">
<svg xmlns="http://www.w3.org/2000/svg" width = "24" height = "24" viewBox = "0 0 24 24"><path d="M8.122 24l-4.122-4 8-8-8-8 4.122-4 11.878 12z"/></svg>
</button>
<div class="Images">
<img src="https://source.unsplash.com/1280x720/?nature">
<img src="https://source.unsplash.com/1280x720/?water">
<img src="https://source.unsplash.com/1280x720/?rock">
<img src="https://source.unsplash.com/1280x720/?abstract">
<img src="https://source.unsplash.com/1280x720/?nature">
<img src="https://source.unsplash.com/1280x720/?trees">
<img src="https://source.unsplash.com/1280x720/?human">
<img src="https://source.unsplash.com/1280x720/?tech">
</div>
</div>
<script src="main.js"></script>
</body>
</html>
So I want to recreate this simple animation effect, which uses a <canavs> :
https://codepen.io/w3devcampus/pen/PpLLKY
HTML:
<html lang="en">
<head>
<meta charset="utf-8">
<title>Draw a monster in a canvas</title>
</head>
<body>
<canvas id="myCanvas" width="200" height="200"></canvas>
</body>
</html>
JavaScript:
function mainLoop() {
ctx.clearRect(0, 0, w, h);
drawMyMonster(xMonster, yMonster);
xMonster += monsterSpeed;
if (((xMonster + 100)> w) || (xMonster < 0)) {
monsterSpeed = -monsterSpeed;
}
}
function drawMyMonster(x, y) {
ctx.save();
ctx.translate(x, y);
ctx.strokeRect(0, 0, 100, 100);
ctx.fillRect(20, 20, 10, 10);
ctx.fillRect(65, 20, 10, 10);
ctx.strokeRect(45, 40, 10, 40);
ctx.strokeRect(35, 84, 30, 10);
ctx.fillRect(38, 84, 10, 10);
ctx.fillRect(52, 84, 10, 10);
ctx.restore();
}
So, I did recreate it, but it wouldn't behave normally. Instead of displaying one frame at a time, it stacks all the frames up each other in the canvas. Here's my version:
https://codepen.io/Undefined_Variable/pen/LraLJQ
HTML:
<html>
<head>
<title>CanvasThingy</title>
</head>
<body>
<canvas id="CanvasForMonster">
No support for you scrub!
</canvas>
</body>
</html>
JavaScript:
var myCanvas, context, w, h;
var xMonster = 60;
var yMonster = 30;
var monsterSpeed = 1;
window.onload = function initial(){
myCanvas = document.body.querySelector('#CanvasForMonster');
w = myCanvas.style.width;
h = myCanvas.style.height;
context = myCanvas.getContext('2d');
ClearCanvas();
}
function ClearCanvas(){
context.clearRect(0, 0, w, h);
DrawFMLface(xMonster, yMonster);
xMonster += monsterSpeed;
if(((xMonster + 175) > 299) || (xMonster < 0) ){
monsterSpeed = -monsterSpeed;
}
requestAnimationFrame(ClearCanvas);
}
function DrawFMLface(x, y){
context.save();
context.translate(x, y);
context.strokeRect(0, 4, 175, 80);
context.strokeStyle = 'black';
context.fillRect(40, 10, 20, 20);
context.strokeStyle = 'black';
context.fillRect(110, 10, 20, 20);
context.strokeStyle = 'black';
context.strokeRect(85, 40, 1, 15);
context.strokeStyle = 'black';
context.strokeRect(42, 70, 90, 0);
context.strokeStyle = 'black';
context.restore();
}
Please tell me how to do this properly without jQuery AT ALL, please. I'm still learning and I don't want any jQuery/Angular/React etc. in my code, please!
Your problem is that the previous faces are not being cleared. The clearing is supposed to be done with this line:
ctx.clearRect(0, 0, w, h);
The problem is that w and h are not set correctly when this is run, so not enough of the canvas is cleared.
This is where you set w and h:
w = myCanvas.style.width;
h = myCanvas.style.height;
This will only work if the width and height were set with CSS, e.g. with an HTML attribute style="width: 200px; height: 200px;", and if you called parseInt to convert the CSS style strings into numbers. But in fact the canvas elementâs width and height were set with width and height HTML attributes directly. So you should set w and h with HTMLCanvasElementâs width and height properties:
w = canvas.width;
h = canvas.height;
The original Codepen sets them this way.
Just in case you were looking for a possibly easier way to do this, you can actually avoid JavaScript entirely when creating a simple animation like this by using CSS animations:
#CanvasForMonster {
position: relative;
width: 200px;
height: 200px;
border: 1px solid #000;
}
#Monster {
position: absolute;
top: 25%;
width: 50%;
height: 50%;
animation: bounce 2s alternate linear infinite;
}
#keyframes bounce {
from { left: 0; }
to { left: 50%; }
}
#Monster div {
position: absolute;
}
#Monster .stroke {
border: 1px solid #000;
}
#Monster .fill {
background-color: #000;
}
#Monster .head {
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#Monster .eye {
width: 10%;
height: 10%;
}
#Monster .eye-left {
left: 25%;
top: 25%;
}
#Monster .eye-right {
left: 65%;
top: 25%;
}
#Monster .nose {
left: 50%;
top: 50%;
width: 1px;
height: 10%;
}
#Monster .mouth {
left: 25%;
top: 75%;
width: 50%;
height: 0;
}
<div id="CanvasForMonster">
<div id="Monster">
<div class="stroke head"></div>
<div class="fill eye eye-left"></div>
<div class="fill eye eye-right"></div>
<div class="stroke nose"></div>
<div class="stroke mouth"></div>
</div>
</div>
Everything you have is alright.
The problem is your Globals.
The width of the canvas should be set to:
w = myCanvas.width;
Height set to:
h = myCanvas.height;
You have both set as:
w = myCanvas.style.width;
h = myCanvas.style.height;
Which will not work.
Here is the fork (Demo).
I would highly recommend using JQuery when working with canvas.
When using canvas it should be noted that you want too reference canvas.height, and canvas.width as much as possible rather then using a global that points at the height, and width of the css style.
Is it possible to change this code so that the balls rolled out.
but:
Indicate the number of balls (for example 10).
Once rolled out one ball and stopped, then rolls out the next ball
(and gets close to another).
And that they rolling from the right side to the left side.
html
<div id="balls">
<img src="http://i058.radikal.ru/1407/0d/33cc119c6686.png" id="ball" />
</div>
css
body {
background: #383838;
}
#balls {
position: absolute;
}
#balls img {
position: absolute;
}
#ball {
width: 90px;
height: 90px;
left: 170px;
top: 45px;
}
jQuery
var diameter = $('#ball').height(),
perimeter = Math.PI * diameter;
var goLeft;
var times = 0;
var to = [600, 600];
function moveBalls() {
goLeft = !goLeft;
times++;
if (times > to.length) {
return false;
}
$('#balls').animate({
right: to[times]
}, {
duration: 2000,
step: rotateBall,
complete: moveBalls
});
}
moveBalls();
function rotateBall(distance) {
var degree = distance * 360 / perimeter;
$('#ball').css('transform', 'rotate(' + degree + 'deg)');
}
Example Here
jsBin demo
To get a better result you should have 3 elements for ball:
one that moves right with a static light source and shadow
rotate the inner DIV element
a SPAN with number -> inside the rotating DIV.
Use CSS3 transitions like I did.
<div id="balls">
<div class="ball blue"> <!-- THIS ONE JUST MOVES RIGHT -->
<div><span>7</span></div> <!-- THIS ONE ROTATES -->
</div>
<!-- MORE BALLS HERE -->
</div>
Following the above logic the CSS ends up being quite trivial:
.ball{
position:absolute;
left:-100px;
width:90px;
height:90px;
background:#004E99;
border-radius:50%;
box-shadow: 20px 30px 30px -10px rgba(0,0,0,0.4);
}
.ball>div{
position:absolute;
width:100%;
height:100%;
border-radius:50%;
}
.ball>div>span{
position:absolute;
left:23px;
top:14px;
width:45px;
height:45px;
border-radius:50%;
text-align:center;
line-height:45px;
font-size:24px;
font-weight:bold;
background:#fff;
}
/* YOUR COLORS */
.ball.blue{ background: radial-gradient(circle at 20px 20px, #09f, #001);}
JS/jQ:
var $ball = $('#balls > div'),
diameter = $ball.height(),
perimeter = Math.PI * diameter,
n = $ball.length,
i = 0,
itv;
itv = setInterval(function(){
if(i>n)clearInterval(itv);
rotateBall( 500-(diameter*i) );
i++;
},2000);
function rotateBall(distance) {
var degree = distance * 360 / perimeter;
$ball.eq(i).css({
transition: "2s",
transform: 'translateX('+ distance +'px)'
}).find('div').css({
transition: "2s",
transform: 'rotate('+ degree +'deg)'
});
}
I have this HTML that renders a simple arrow sign pointing towards the right:
<!DOCTYPE html>
<html>
<head>
<style>
div { width: 0px; height: 0px; border-left: 20px solid black; border-top: 20px solid transparent; border-bottom: 20px solid transparent; border-right: 20px solid transparent; position: absolute; left: 35px; top: 53px; cursor: pointer; }
</style>
<body>
<div></div>
</body>
</html>
If you hover of it, the cursor turns to pointer. But because it is actually a square div, the cursor turns pointer even if you are just outside the arrow within the perimeter of the div.
So I wrote this Javascript addition such that the cursor turns pointer only when the mouse is hovering over that arrow. For this purpose, I figured the coordinates of the three vertices of the triangle from Firebug ((35,53),(55,73),(35,93) clockwise from top). Then I check whether the point in question lies inside the triangle formed by these 3 vertices. This I do by checking whether the point and the opposite vertex for each edge lies on the same side of that edge or not (if they do, the product of the values obtained by substituting the coordinates of that point for x and y in that equation will be positive).
<!DOCTYPE html>
<html>
<head>
<style>
div { width: 0px; height: 0px; border-left: 20px solid black; border-top: 20px solid transparent; border-bottom: 20px solid transparent; border-right: 20px solid transparent; position: absolute; left: 35px; top: 53px; }
.hoverclass { cursor: pointer; }
</style>
<script src="jquery.js">
</script>
<script>
$(document).ready(function(){
$("div").click(function(e) { alert(e.pageX + " " + e.pageY); });
function l1(x,y) { return y - x - 18; }
function l2(x,y) { return x+y-128; }
function l3(x,y) { return x-35; }
$("div").hover(function(e) {
var x = e.pageX;
var y = e.pageY;
if (l1(x,y)*l1(35,93) >= 0 && l1(x,y)*l1(35,93) >= 0 && l1(x,y)*l1(35,93) >= 0 ) {
$(this).addClass('hoverclass');
}
else { $(this).removeClass('hoverclass'); }
},
function() {
$(this).removeClass('hoverclass');
});
});
</script>
<body>
<div></div>
</body>
</html>
However, the results are not predictable. Sometimes the cursor turns pointer within the triangle only, sometimes outside as well (just as before), and sometimes not at all. I suspect that this is probably due to the hover function working overtime, that may be temporarily hanging the script. Is there any other way to achieve this?
This could be done using HTML5 canvas. Basic idea is to check for pixel color on mousemove on canvas element. This way, your element can be of any form as you wish. Of course, you should make some optimization of following code:
SEE WORKING DEMO
function findPos(obj) {
var curleft = 0, curtop = 0;
if (obj.offsetParent) {
do {
curleft += obj.offsetLeft;
curtop += obj.offsetTop;
} while (obj = obj.offsetParent);
return { x: curleft, y: curtop };
}
return undefined;
}
// set up triangle
var example = document.getElementById('example');
var context = example.getContext('2d');
context.fillStyle = '#000';
context.strokeStyle = '#f00';
context.lineWidth = 1;
context.beginPath();
// Start from the top-left point.
context.moveTo(10, 10); // give the (x,y) coordinates
context.lineTo(60, 60);
context.lineTo(10, 120);
context.lineTo(10, 10);
// Done! Now fill the shape, and draw the stroke.
// Note: your shape will not be visible until you call any of the two methods.
context.fill();
context.stroke();
context.closePath();
$('#example').mousemove(function(e) {
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
var coord = "x=" + x + ", y=" + y;
var c = this.getContext('2d');
var p = c.getImageData(x, y, 1, 1).data;
if(p[3]!='0') $(this).css({cursor:'pointer'});
else $(this).css({cursor:'default'});
});
You'd better use CSS instead. With :before and :after pseudo classes you can do magic. Check out this Pure CSS GUI icons by Nicolas Gallagher.
If you use any CSS pre-processor, these icons can be wrapped up as a mixin, this way required properties can be assigned like this:
#icon > .close(16px, #fff, #E83921);
You can make any shape have cursor pointer with CSS only. The idea is to rotate wrapper container which has overflow: hidden (you can have several of them depending on the shape you need). In case of OP problem this code does a trick:
<div class="arrow"><i></i></div>
.arrow {
margin: 100px;
border_: 1px red solid;
width: 40px;
height: 40px;
overflow: hidden;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
.arrow i {
height: 65px;
width: 65px;
background-color: green;
content: '';
display: block;
cursor: pointer;
margin: -35px 0 0 11px;
-webkit-transform: rotate(45deg);
transform: rotate(45deg);
}
See this demo: http://cssdesk.com/PaB5n
True that this requires CSS transform support so it's not cross browser.