So, for our project, we have to make a Wheel of Fortune game (based on the game show), and I made everything (like checking if the word was correct and everything) but I still don't know how to do the animations. What I have to animate is similar to what is in the attached picture. Our teacher told us that we can't use canvas, so I can't use .rotate(). So, does anyone have any ideas as to how this can be accomplished? Remember, I don't even need the wheel to actually rotate; it would work even if just the pictures were to change positions.
Here is the picture of a sample wheel (pretend the numbers are all pictures). I have stored all of the pictures in a single array that contains all of the pictures:
https://en.m.wikipedia.org/wiki/Wheel_of_Fortune_(U.S._game_show)#/media/File%3AWheel_of_Fortune_Round_1_template_Season_31.png
PS: For academic honesty purposes, I will not be able to post my code here.
Thank you for your time, everyone!
This is a very basic way of doing it: http://codepen.io/AaronGeorge/pen/zoOWagDead link
What you utilise is the CSS transform property and use the rotate value.
You'll need to do some math to work out how much you need to rotate the image, but transform: rotate(amount); will do the trick without using canvas.
The following example is a slight remake of How to Draw a Wheel of Fortune - without using HTML Canvas
Create an array of the sectors values, starting from right - clockwise.
Generate a random ang (in radians)
Animate CSS's rotate property using JS's Animations API at a random duration
Create a function getIndex() to get the prize sector index
Assign a "finish" Event listener to get the landed prize sector
const sectors = [
"500", "700", "BMB", "600", "550", "500", "600", "B",
"650", "FP", "700", "LAT", "800", "500", "650", "500",
"900", "B", "2500", "W", "600", "700", "600", "650",
];
// Generate random float in range min-max:
const rand = (m, M) => Math.random() * (M - m) + m;
const tot = sectors.length;
const elWheel = document.querySelector("#wheel");
const elAng = document.querySelector("#ang");
const PI = Math.PI;
const TAU = 2 * PI;
let arc = TAU / tot;
let ang = 0; // Angle rotation in radians
let isSpinning = false;
const imageAngOffset = -arc / 2;
// Get index of current sector
const getIndex = () => Math.floor(tot - (ang % TAU) / TAU * tot) % tot;
const spin = () => {
if (isSpinning) return; // Do nothing
isSpinning = true;
ang += rand(20, 30); // Generate random angle
const anim = elWheel.animate([{rotate: `${ang}rad`}], {
duration: rand(4000, 5000),
easing: "cubic-bezier(0.23, -0.16, 0.2, 1)",
fill: "both"
});
anim.addEventListener("finish", (event) => {
isSpinning = false;
ang += imageAngOffset; // Fix for image rotation offset (half arc)
const index = getIndex();
const value = sectors[index];
console.clear();
console.log(value);
});
};
elWheel.addEventListener("pointerdown", spin);
* {margin: 0;}
#wheel-wrapper {
overflow: hidden;
position: relative;
margin: auto;
width: 90vmin;
aspect-ratio: 1;
display: flex;
/* rotate: -0.25turn; */
}
#wheel {
position: relative;
margin: auto;
width: 90%;
aspect-ratio: 1;
background-image: url(https://i.stack.imgur.com/mcuwP.png);
background-size: cover;
border-radius: 50%;
}
#wheel-wrapper::after {
content: "";
position: absolute;
right: 0;
width: 10%;
background: red;
height: 1%;
transform: translateY(-50%);
top: 50%;
}
<div id="wheel-wrapper">
<div id="wheel"></div>
</div>
Since the rightmost starting sector is not at a perfect 0 degree in the image, don't forget to fix for this by subtracting half arc to the end degree radians ang (see in example above the use of imageAngleOffset).
To rotate the entire wheel wrapper by negative quarter turn (so that the needle stays at the top), uncomment this line in CSS: rotate: -0.25turn;
Related
I have a container that is expanded and collapsed on click of chevron icon. The code to collapse/expand the container is in the function transformAnimation. The code of transformAnimation is similar to the code on MDN web docs for requestAnimationFrame. The code to animate (scale) the container has been developed on the guidelines of this article on Building performant expand & collapse animations on Chrome Developers website.
I am not able to figure out how to calculate yScale value (which is nothing but the css function scaleY() for collapse/expand animation) as a function of the time elapsed since the start of the animation.
To elaborate what I mean, let's assume that the container is in expanded state. In this state the yScale value of the container is 6. Now when user clicks on the toggle button, in the transformAnimation function for each animation frame, i.e, execution of the requestAnimationFrame callback step function, the value of yScale should decrease from 6 (the expanded state) to 1 (the collapsed state) in the exact duration that I want the animation to run for. So, basically I want to achieve something similar to css property transition-duration: 2s, where I can control the duration.
In the present state, the code to calculate yScale is not working as expected.
const dragExpandableContainer = document.querySelector('.drag-expandable-container');
const dragExpandableContents = document.querySelector('.drag-expandable__contents');
const resizeableControlEl = document.querySelector('.drag-expandable__resize-control');
const content = document.querySelector(`.content`);
const toggleEl = document.querySelector(`.toggle`);
const collapsedHeight = calculateCollapsedHeight();
/* This height is used as the basis for calculating all the scales for the component.
* It acts as proxy for collapsed state.
*/
dragExpandableContainer.style.height = `${collapsedHeight}px`;
// Apply iniial transform to expand
dragExpandableContainer.style.transform = `scale(1, 10)`;
// Apply iniial reverse transform on the contents
dragExpandableContents.style.transform = `scale(1, calc(1/10))`;
let isOpen = true;
const togglePopup = () => {
if (isOpen) {
collapsedAnimation();
toggleEl.classList.remove('toggle-open');
isOpen = false;
} else {
expandAnimation();
toggleEl.classList.add('toggle-open');
isOpen = true
};
};
function calculateCollapsedHeight() {
const collapsedHeight = content.offsetHeight + resizeableControlEl.offsetHeight;
return collapsedHeight;
}
const calculateCollapsedScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = dragExpandableContainer.getBoundingClientRect().height;
return {
/* Since we are not dealing with scaling on X axis, we keep it 1.
* It can be inverse to if required */
x: 1,
y: expandedHeight / collapsedHeight,
};
};
const calculateExpandScale = function() {
const collapsedHeight = calculateCollapsedHeight();
const expandedHeight = 100;
return {
x: 1,
y: expandedHeight / collapsedHeight,
};
};
function expandAnimation() {
const {
x,
y
} = calculateExpandScale();
transformAnimation('expand', {
x,
y
});
}
function collapsedAnimation() {
const {
x,
y
} = calculateCollapsedScale();
transformAnimation('collapse', {
x,
y
});
}
function transformAnimation(animationType, scale) {
let start, previousTimeStamp;
let done = false;
function step(timestamp) {
if (start === undefined) {
start = timestamp;
}
const elapsed = timestamp - start;
if (previousTimeStamp !== timestamp) {
const count = Math.min(0.1 * elapsed, 200);
//console.log('count', count);
let yScale;
if (animationType === 'expand') {
yScale = (scale.y / 100) * count;
} else yScale = scale.y - (scale.y / 100) * count;
//console.log('yScale', yScale);
if (yScale < 1) yScale = 1;
dragExpandableContainer.style.transform = `scale(${scale.x}, ${yScale})`;
const inverseXScale = 1;
const inverseYScale = 1 / yScale;
dragExpandableContents.style.transform = `scale(${inverseXScale}, ${inverseYScale})`;
if (count === 200) done = true;
//console.log('elapsed', elapsed);
if (elapsed < 1000) {
// Stop the animation after 2 seconds
previousTimeStamp = timestamp;
if (!done) requestAnimationFrame(step);
}
}
}
requestAnimationFrame(step);
}
.drag-expandable-container {
position: absolute;
bottom: 0px;
display: block;
overflow: hidden;
width: 100%;
background-color: #f3f7f7;
transform-origin: bottom left;
}
.drag-expandable__contents {
transform-origin: top left;
}
.toggle {
position: absolute;
top: 2px;
right: 15px;
height: 10px;
width: 10px;
transition: transform 0.2s linear;
}
.toggle-open {
transform: rotate(180deg);
}
.drag-expandable__resize-control {
background-color: #e7eeef;
}
.burger-icon {
width: 12px;
margin: 0 auto;
padding: 2px 0;
}
.burger-icon__line {
height: 1px;
background-color: #738F93;
margin: 2px 0;
}
.drag-expandable__resize-control:hover {
border-top: 1px solid #4caf50;
cursor: ns-resize;
}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="css.css">
</head>
<body>
<div class="drag-expandable-container">
<div class="drag-expandable__contents">
<div class="drag-expandable__resize-control">
<div class="burger-icon">
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
<div class="burger-icon__line"></div>
</div>
</div>
<div class="content" />
<div>
<div class="toggle toggle-open" onclick="togglePopup()">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--! Font Awesome Pro 6.1.1 by #fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2022 Fonticons, Inc. --><path d="M416 352c-8.188 0-16.38-3.125-22.62-9.375L224 173.3l-169.4 169.4c-12.5 12.5-32.75 12.5-45.25 0s-12.5-32.75 0-45.25l192-192c12.5-12.5 32.75-12.5 45.25 0l192 192c12.5 12.5 12.5 32.75 0 45.25C432.4 348.9 424.2 352 416 352z"/></svg>
</div>
</div>
</div>
</div>
</body>
<script type="text/javascript" src="js.js"></script>
</html>
Posting this as an answer for better code formatting.
It sounds like what you are trying to do can be achieved with something called Linear Interpolation (commonly known as "lerp" or "lerping" amongst developers).
Have a look at the following function:
function lerp(v0, v1, t) {
return v0*(1-t)+v1*t
}
This is probably the simplest possible function for linear interpolation, but it should be good enough for what you are trying to calculate. The function takes three parameters:
v0, the initial value, for example 0
v1, the final value, for example 10
t, a value between 0 and 1, representing the "percentage of progress" from v0 to v1
So for example, if you were to call lerp(0, 10, 0.5), the function would return 5, since 5 is "50% of the way" going from 0 to 10.
If you were to call lerp(0, 10, 0.9), the function would return 9.
Furthermore with t = 0, the function returns 0 and with t = 10, the function returns 10.
So applying this to your problem, you have two container heights:
y0 = container height when minimized (0)
y1 = container height when maximized (6)
and then you have the elapsed time (et) and the total time (tt) how long it should take for the container to open and close (2 seconds).
We want to make lerp to return the yScale at some point in time t.
We already have two of the lerp parameters, v0 = y0 and v1 = y1, but we can't directly use et as the t parameter, because et goes from 0 to 2 and t has to go from 0 to 1.
To fix this, we have to scale et to go from 0 to 1.
Here is a full example code showing the scaling and the usage of lerp:
// Y scale value when minimized
let y0 = 0;
// Y scale value when maximized
let y1 = 6;
// Time it should take to go from 0 to 6
let totalTime = 2000;
// The lerp function
function lerp(v0, v1, t) {
return v0*(1-t)+v1*t
}
// A loop going from 0ms to 2000ms to simulate how the lerp function works
// We have to use integers and therefore milliseconds with 100ms steps here,
// because JavaScript does not like incrementing by 0.1
for(let elapsed_time = 0; elapsed_time <= totalTime; elapsed_time += 100)
{
// Scale elapsed time to go from 0 to 1, instead of 0 to 2
let elapsed_time_scaled = elapsed_time / totalTime;
// Get y scale with lerp at the current time
let scale_y = lerp(y0, y1, elapsed_time_scaled).toFixed(2);
// Log the stuff out
console.log(elapsed_time, elapsed_time_scaled, scale_y);
}
Which returns something like this:
0 0 '0.00'
100 0.05 '0.30'
200 0.1 '0.60'
300 0.15 '0.90'
400 0.2 '1.20'
500 0.25 '1.50'
600 0.3 '1.80'
700 0.35 '2.10'
800 0.4 '2.40'
900 0.45 '2.70'
1000 0.5 '3.00'
1100 0.55 '3.30'
1200 0.6 '3.60'
1300 0.65 '3.90'
1400 0.7 '4.20'
1500 0.75 '4.50'
1600 0.8 '4.80'
1700 0.85 '5.10'
1800 0.9 '5.40'
1900 0.95 '5.70'
2000 1 '6.00'
Looks good to me! Would this help you?
Im trying to point the red div towards the corner of the window using transform rotate.
The Yellow is 45deg fixed, just for reference.
The Blue points to the left top corner using the innerHeight and innerWidth as points.
And the Red trys to mimic the Blue by calculating 45 + some offset, it must aways have the same rotation as the Blue but without using innerHeight and innerWidth as points.
This is the closest i got of makeing it work was using this code:
window.onresize = () => calcAngle()
var calcAngle = () => {
console.clear()
var x1 = 0, y1 = 0;
var x2 = window.innerWidth, y2 = window.innerHeight;
var a = (Math.atan2((y2 - x1), (x2 - y1)) * (180 / Math.PI));
document.querySelectorAll(".pointer")[1].style.transform = "translate(50%, -50%) rotate("+a+"deg)"
var of = x2/y2;
var ang = 45;
var calc = ang - (ang*of-ang)
document.querySelectorAll(".pointer")[2].style.transform = "translate(50%, -50%) rotate("+(calc)+"deg)"
console.log(a, calc)
}
calcAngle();
body {
overflow: hidden;
}
.pointer {
width: 200px;
height: 20px;
opacity: .7;
background: blue;
position: absolute;
top: 50%;
right: 50%;
transform: translate(50%, -50%);
clip-path: polygon(100% 0%, 100% 100%, 0% 50%);
}
.pointer:nth-child(1){
background: yellow;
transform: translate(50%, -50%) rotate(45deg);
}
.pointer:nth-child(3){
background: red;
}
<div class="pointer"></div>
<div class="pointer"></div>
<div class="pointer"></div>
Using the code as example, calc must always have the same value of a but using 45 deg as reference.
Of course as you show in your question, the simplest approach is to use the atan(y/x) * 180 / PI to get the entire angle. This is reflected below as refAngle.
Since your condition requires that it be an offset of 45 degrees, this requires more advanced math, using the law of sines in addition to basic trigonometry. We have enough information based on the ratio of width/height of the screen to find the information, but it ends up being a very complex formula. This is reflected below in two steps, first sinOff to get the sine of the offset angle relative to 45 degrees, and then off once we've done the asin and conversion from radians to degrees.
This snippet demonstrates that the two angles agree, no matter how the browser window is resized.
const x = window.innerWidth;
const y = window.innerHeight;
const { sin, atan, asin, sqrt, PI } = Math;
const sinOff = sin(atan(y/x)) / (sqrt(2)*y) * (x-y);
const off = asin(sinOff) * 180 / PI;
const angle = 45 - off;
const refAngle = atan(y/x) * 180 / PI;
console.log(angle, refAngle);
Note, since the formula is so complex, I'm using destructuring to reduce the Math. clutter.
I want to arrange some rectangular div components around a regular polygon. Basically one of the long sides of the divs will be coincident with a line segment around the polygon.
In the final code, I'll use .ejs (since the number of sides of the polygon is dynamic, 3-10 sides). In my "quick and dirty" testing I'm doing a triangle in just HTML and CSS to get the math right.
I have a "very close" solution already and am wondering how to get it "exact" and am also wondering why my geometry intuition is so far off.
HTML and CSS:
div {
position: absolute;
left: 200px;
top: 200px;
width: 80px;
height: 40px;
background-color: skyblue;
}
.rotatedA {
transform: translateY(-60px) translateX(-35px) rotate(300deg);
background-color: blue;
}
.rotatedB {
transform: translateY(-60px) translateX(35px) rotate(60deg);
background-color: red;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>title</title>
<link rel="stylesheet" href="basic.css">
</head>
<body>
<div>Normal</div>
<div class="rotatedA">Rotated</div>
<div class="rotatedB">Rotated</div>
</body>
</html>
The first attempt I rotated "A" by 60 and "B" by -60 and did a translateY equal to the div height. When that did not work I played around with it.
On this last attempt (close but not perfect since the rotations won't give an integer) it seems like the Y adjustment is 1.5x (item height + cos(60)) but the X adjustment is 1/2 of sin(60) (I don't understand why).
Since my results aren't going to be an integer number of pixels what is the correct way to do this? Also, I don't understand why my geometry is so off (I could understand sin(60) but 1/2(sin(60)) doesn't make sense to me
Here's a mathematical way; the number and dimensions are read by the script, then the divs are arranged accordingly. I also made sure that the wrapper container has the correct dimensions so it can be used with other elements:
function arrange(wrapper) {
wrapper.style.position = "relative";
const rects = Array.from(wrapper.children);
const n = rects.length;
/* dimensions of a rectangle */
const bb = rects[0].getBoundingClientRect();
const a = bb.width;
const h = bb.height;
/* incircle radius of regular polygon */
const r = a * 0.5 / Math.tan(Math.PI / n);
/* radius of outer circle */
const bigR = Math.sqrt((r + h) * (r + h) + a * a / 4);
rects.forEach((rect, i) => {
const angle = i * (360 / n);
if (angle) rect.style.transform = `rotate(${angle}deg)`;
rect.style.position = angle ? "absolute" : "relative";
rect.style.marginBottom = bigR + r + "px";
rect.style.transformOrigin = `${a/2}px ${-r}px`;
rect.style.left = bigR - a / 2 + "px";
rect.style.top = bigR + r + "px";
});
if (window.getComputedStyle(wrapper).display == "inline-block")
wrapper.style.width = 2 * bigR + "px";
}
arrange(document.querySelector('#polygon'));
#polygon {
border: 1px solid black;
display: inline-block;
}
#polygon div {
width: 80px;
height: 20px;
background-color: skyblue;
text-align: center;
padding: 5px;
}
<div id="polygon">
<div>Normal</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
<div>Rotated</div>
</div>
The basic idea is to
calculate the in-circle's radius of the polygon based on the width of a rectangle
set transform-origin accordingly centered and above the first rectangle
arrange the others by rotating them
(do more calculations so the wrapper element encompasses everything exactly)
Hi I am using a hashmap that allows me to efficiently detect objects in the given coordinates. However it is working perfectly , the problem lies with using the mouse to gather the position of the mouse within the canvas down to the pixel. I have been using the offsetX and offsetY methods for the event to gather some offset but it seems there is an offset I am unaware of and may have something to do with either:
1.using scaling on the canvas , Note: ive tried to fix this by division of the renderscale, this works with everything else so should be fine here.
mouseoffset is not accounting for parts of the page or is missing pixels at a low level (maybe 20) but divided by the render scale thats massive.
3.I am using a cartesian coordinate system to simplify things for the future , so the game map is in cartesian and may have to do with the problem.
I will not be supplying all the code because it is allot of work to go through it all so i will supply the following :
the html/css canvas code
<html lang="en">
<head>
<meta charset="UTF-8">
<title> Game</title>
</head>
<body onload="jsEngine = new JsEngine(24, 24, .1); " >
<div class ="wrapper">
<canvas id="canvas" width="1920" height="1080"></canvas>
</div>
<style>
.wrapper {
position: relative;
width: auto;
height: 900px;
}
.wrapper canvas {
position: absolute;
left: 90px;
top: 50px;
padding-left: 0;
padding-right: 0;
margin-left: auto;
margin-right: auto;
display: block;
width: 90%;
height: 90%;}
.GUI{
top: -315px;
left: -302px;
position: absolute;
width: 300px;
height: 300px;
background-color: cadetblue;
opacity: .5;
word-wrap: break-word;}
img{
image-rendering: optimize-contrast;
}
</style>
<div id = GUI class = "GUI"></div>
<!-- Libraries -->
<script src="../myapi/JSONE.js"></script>
<script src="../myapi/engine/SpacialHash.js"></script>
</body>
</html>
2.the javascript click function
//Click on objects
let onClick = function(event){
let canvas_ctx = document.getElementById("canvas").getContext("2d");
let canvasOffsetX = canvas_ctx.canvas.width/2;
let canvasOffsetY = canvas_ctx.canvas.height/2;
let mousePosX = event.clientX;
let mousePosY = event.clientY;
let mouseX =jsEngine.cameraFocus.x-canvasOffsetX/jsEngine.renderScale+(mousePosX)/jsEngine.renderScale;
let mouseY = jsEngine.cameraFocus.y+(canvasOffsetY)/jsEngine.renderScale+((-mousePosY)/jsEngine.renderScale);
console.log("sum to",mouseX,mouseY);
//My hashMap to place the mouse coordinates on the game map
let clickPosition = hm.find({x:mouseX,y:mouseY,width:1,height:1});
if(clickPosition.length===1){
let gameObject = jsEngine.gameObjects[clickPosition[0].range.id];
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY);
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
}else if(clickPosition.length>1) {
for (let i = 0; i < clickPosition.length; i++) {
let gameObject = jsEngine.gameObjects[clickPosition[i].range.id];
if (gameObject instanceof PlayerShip|| gameObject instanceof Bullet)
continue;
let clickBox = {};
let picture = gameObject.texture;
guiCreateClickBox(clickBox,gameObject.id,1200,500,picture);
//console.log(gameObject.transform.x,gameObject.transform.y,mouseX,mouseY)
}
}
};
// Listeners
//Click on objects
document.getElementById("canvas").addEventListener("click", onClick);
the making of the map and scale :Note: this is done via onPreRender
function drawBackground(canvas_ctx, renderScale, imageResource) {
let img = imageResource.mapBackground;
let mapWidth = 1000000;
let mapHeight= 1000000;
let zoom = 1;
mapWidth *= renderScale / zoom;
mapHeight *= renderScale / zoom;
// Render the Background
canvas_ctx.fillStyle = canvas_ctx.createPattern(img, 'repeat');
canvas_ctx.scale(zoom, zoom);
canvas_ctx.fillRect(-mapWidth / 2, - mapHeight / 2, mapWidth, mapHeight);
//if (jsEngine.cameraFocus.x > 1000000) {}
canvas_ctx.scale(1/zoom, 1/zoom);
}
The rendering method used for playership
renderGameObject(gameObject) {
let x = gameObject.transform.x * this.renderScale;
let y = -(gameObject.transform.y * this.renderScale);
let rotation = Math.radians(gameObject.transform.rotation);
let width = gameObject.transform.width;
width *= this.renderScale;
let height = gameObject.texture.height;
height *= this.renderScale;
// Render the gameObject
this.canvas_ctx.translate(x, y);
this.canvas_ctx.rotate(rotation);
this.canvas_ctx.drawImage(gameObject.texture, 0, 0, width / this.renderScale, height / this.renderScale, // Make sure the image is not cropped
-width/2 , // X
-height/2 , // Y
width, height); // width and height
this.canvas_ctx.rotate(-rotation);
this.canvas_ctx.translate(-x, -y);
}
the issue to solve is to make it so that when you click on any given quadrant of the canvas it will return -+ for top left, -- bottom left , -+ topright, +- bottomright, as well as being applied to the render scale which at the moment is .1 so just divide your mouse and canvas coords like shown above and you should be able to get the same results.
Things to keep in mind :
the jsEngine.cameraFocus is set to the playerships x and y coordinates(which are set to the 0,0 posiiton on the map) (which are also in the middle of the ship)
the top left of the canvas is still 0,0 and ++ is still toward the bottom right so theoretically minusing half the canvas width/height then adding the offsets X and Y. this should be working but at my map coordinate -4000,-4000 i get ~-3620,-3295 and at +4000,+4000 I get 3500,3500. (The reason why the canvas 0,0 is not where the ship is , is to make the ship in the middle of the screen)
If you have questions about anything based on code that needs to be supplied please ask via comment . Please note if you have problems with the format of the code supplied I have nothing to say about it . all I need is the click function working on the canvas model i set up in cartesian format.
ps: jQuery is not a solution its a problem please use vanilla js.
I found out why it was off , my canvas has a offset of 90 px and 50 px as well as the main problem that the canvas is only 90% of its origonal size (also in css). If anyone can give me help for how to adjust to these issues please reply in comment . until then I beleieve I have solved my own issue .
I'm programming snake with Javascript. For the background of the different body parts I'm using the following gradient generation:
gibGradient: function() {
var string = "background: linear-gradient(to right, rgba(243,226,199,1) 15%,rgba(193,158,103,1) "+ snake.data.gradientLinks +"%,rgba(182,141,76,1) "+ snake.data.gradientRechts +"%,rgba(233,212,179,1) 90%);";
if ((snake.data.gradientLinks < 85) && (snake.data.modus == "hochzaehlen")) {
snake.data.gradientLinks = snake.data.gradientLinks + 5;
snake.data.gradientRechts = snake.data.gradientRechts + 5;
if (snake.data.gradientLinks >= 85) {
snake.data.modus = "runterZaehlen";
}
}
if ((snake.data.gradientLinks > 20) && (snake.data.modus == "runterZaehlen")) {
snake.data.gradientLinks = snake.data.gradientLinks - 5;
snake.data.gradientRechts = snake.data.gradientRechts - 5;
if (snake.data.gradientLinks <= 20) {
snake.data.modus = "hochzaehlen";
}
}
return string;
},
My problem is that when the snake moves and it changes directions, the gradient needs to be bent to fit in the last body part before the corner ends and the last that follows the straight of the snake.
For Example:
Im using 10x10 px div elements
Now i need the transition when it moves a corner
Anybody got an idea?
I took the time to write a few utility javascript functions you may find useful. They require the use of the jQuery library however. The best way to create bent gradients is to use offset radial gradients. This combined with border radius makes for a really nice effect.
Now it is up to you to
use the right function at the right times (the naming convention of
the functions is sideA_To_sideB so rightToUp means going right will
eventually find sideA and going up will eventually find sideB - sides
being the head or the tail)
make it cross browser (if you are into that sort of thing)
rounding the head and tail would be a nice touch (ideally this rounding would only occur on
vertical and horizontal parts)
Feel free to change the size variable to suit your needs.
EDIT - based on the image you just added I created this : jsfiddle http://jsfiddle.net/Lbydhhkh/. This was done using rotated linear gradients. I still think using my original approach looks better and makes more sense. This should be enough to send you in the right direction though. The pseudocode can still be used for this new code.
var size = 40;
function aToB(gradient) {
return $("<div>").css({
width: size,
height: size,
background: gradient,
position: "absolute"
});
}
function radialOut(x, y, corner) {
var css = {};
css["border-" + corner + "-radius"] = size / 2;
return aToB([
"radial-gradient(",
size,
"px at ",
x,
"px ",
y,
"px, red, blue)"].join("")).css(css);
}
function radialIn(x, y, corner) {
var css = {};
css["border-" + corner + "-radius"] = size / 2;
return aToB([
"radial-gradient(",
size,
"px at ",
x,
"px ",
y,
"px, blue, red)"].join("")).css(css);
}
function downToUp() {
return aToB("linear-gradient(to left, red, blue)");
}
function rightToLeft() {
return aToB("linear-gradient(to bottom, red, blue)");
}
function upToDown() {
return aToB("linear-gradient(to right, red, blue)");
}
function leftToRight() {
return aToB("linear-gradient(to top, red, blue)");
}
function upToRight() {
return radialIn(size, 0, "bottom-left");
}
function leftToUp() {
return radialIn(0, 0, "bottom-right");
}
function downToLeft() {
return radialIn(0, size, "top-right");
}
function rightToDown() {
return radialIn(size, size, "top-left");
}
function rightToUp() {
return radialOut(size, 0, "bottom-left");
}
function upToLeft() {
return radialOut(0, 0, "bottom-right");
}
function leftToDown() {
return radialOut(0, size, "top-right");
}
function downToRight() {
return radialOut(size, size, "top-left");
}
$(function () {
//inner
$("body").append(upToDown().css({
top: size,
left: 0
})).append(upToRight().css({
top: size * 2,
left: 0
})).append(leftToRight().css({
top: size * 2,
left: size
})).append(leftToUp().css({
top: size * 2,
left: size * 2
})).append(downToUp().css({
top: size,
left: size * 2
})).append(downToLeft().css({
top: 0,
left: size * 2
})).append(rightToLeft().css({
top: 0,
left: size
})).append(rightToDown().css({
top: 0,
left: 0
}));
//outer
$("body").append(leftToDown().css({
top: 0,
left: size * 5
})).append(upToDown().css({
top: size,
left: size * 5
})).append(upToLeft().css({
top: size * 2,
left: size * 5
})).append(rightToLeft().css({
top: size * 2,
left: size * 4
})).append(rightToUp().css({
top: size * 2,
left: size * 3
})).append(downToUp().css({
top: size * 1,
left: size * 3
})).append(downToRight().css({
top: 0,
left: size * 3
})).append(leftToRight().css({
top: 0,
left: size * 4
}));
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Also here is some pseudocode to help you call the appropriate functions
while(nextPart()) { //while there are more parts to process
var prev = getPrev(), //returns null or previous part
curr = getCurrent(), //returns current part
next = getNext(), //returns null or next part
a, b, part = [];
//get the direction towards the tail
if(prev) a = curr.getDirectionTo(prev); //returns "up", "right", "down", or "left"
else a = tail.getOppositeDirection(); //returns "up", "right", "down", or "left"
//get the direction towards the head
if(next) b = curr.getDirectionTo(next);
else b = head.getDirection(); //returns "up", "right", "down", or "left"
b = upperCaseFirstLetter(b);
if(!prev) part.push("tail"); //is this a tail?
if(!next) part.push("head"); //is this a head?
//the following line of code calls a function with the form "aToB"
//the variable part does not do anything yet but it can help the called
//function determine if this part is a head, tail, or both for rounding
var domElement = window[a + "To" + b](part);
domElement.css(curr.position()); //properly position the element
$("#container").append(domElement); //add the element to the container
}