<div id="card">
<canvas id="canvas" width="640" height="480"></canvas>
</div>
<div id="signature">
Just drag the pink slider to adjust the temperature!<br />
Made with ❤ by <a href="https://idreesinc.com?utm_source=codepen&utm_medium=signature&utm_campaign=temperature-simulation" target="_blank">Idrees Hassan</a> - MIT License - Source available <a href="https://idreesinc.com/about-temperature-simulation?utm_source=codepen&utm_medium=signature&utm_campaign=temperature-simulation" target="_blank">here</a>
</div>
<script src="./application.js"></script>
html {
width: 100%;
height: 100%;
min-height: 500px;
}
body {
background: linear-gradient(to top, #d2dae9, white);
margin: 0;
background-repeat: no-repeat;
background-attachment: fixed;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
#card {
min-width: 640px;
min-height: 480px;
box-shadow: 0px 50px 50px rgba(0, 0, 0, 0.35);
background: #31323f;
touch-action: none;
}
#canvas {
position: absolute;
pointer-events: none;
}
#signature {
margin-top: 10px;
padding-bottom: 20px;
text-align: center;
margin-left: auto;
margin-right: auto;
font-family: "Courier New", Courier, monospace;
color: black;
user-select: none;
}
a,
a:visited {
color: inherit;
}
/*jshint esversion: 6 */
let canvas;
let ctx;
let arc;
let colorArray = [];
let scale = 0;
let scaleControl = {
x: 540,
y: 130,
width: 50,
barHeight: 300,
handleHeight: 20,
handleY: 0
};
let mouse = {
x: -1,
y: -1,
clickX: -1,
clickY: -1,
isClicked: false,
isDragging: false,
DRAG_THRESHOLD: 10
};
let scaleDragged = false;
let particles = [];
let NUM_OF_PARTICLES = 20;
let clearAway = true;
$(document).ready(function () {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
let grey = {
startColor: "rgba(255, 255, 255, 1.0)",
endColor: "rgba(85, 80, 103, 1.0)"
};
let freezing = {
startColor: "rgba(102, 189, 204, 1.0)",
endColor: "rgba(0, 104, 139, 1.0)"
};
let cool = {
startColor: "rgba(149, 219, 210, 1.0)",
endColor: "rgba(102, 189, 204, 1.0)"
};
let neutral = {
startColor: "rgba(168, 219, 52, 1.0)",
endColor: "rgba(106, 204, 101, 1.0)"
};
let warm = {
startColor: "rgba(255, 206, 82, 1.0)",
endColor: "rgba(255, 151, 109, 1.0)"
};
let hot = {
startColor: "rgba(255, 195, 113, 1.0)",
endColor: "rgba(255, 95, 109, 1.0)"
};
colorArray = [freezing, cool, neutral, warm, hot];
$("#card")
.mousedown((e) => {
let x = e.clientX - $("#card").offset().left;
let y = e.clientY - $("#card").offset().top;
mouse.x = x;
mouse.y = y;
mouse.clickX = x;
mouse.clickY = y;
mouse.isClicked = true;
})
.mousemove((e) => {
let x = e.clientX - $("#card").offset().left;
let y = e.clientY - $("#card").offset().top;
mouse.x = x;
mouse.y = y;
if (
mouse.isClicked &&
(Math.abs(mouse.clickX - x) > mouse.DRAG_THRESHOLD ||
Math.abs(mouse.clickY - y) > mouse.DRAG_THRESHOLD)
) {
mouse.isDragging = true;
}
})
.mouseup((e) => {
let x = e.clientX - $("#card").offset().left;
let y = e.clientY - $("#card").offset().top;
mouse.x = x;
mouse.y = y;
mouse.clickX = -1;
mouse.clickY = -1;
mouse.isDragging = false;
mouse.isClicked = false;
});
$("#card").on("touchstart", (e) => {
let x = e.touches[0].clientX - $("#card").offset().left;
let y = e.touches[0].clientY - $("#card").offset().top;
mouse.x = x;
mouse.y = y;
mouse.clickX = x;
mouse.clickY = y;
mouse.isClicked = true;
});
$("#card").on("touchmove", (e) => {
let x = e.touches[0].clientX - $("#card").offset().left;
let y = e.touches[0].clientY - $("#card").offset().top;
mouse.x = x;
mouse.y = y;
if (
mouse.isClicked &&
(Math.abs(mouse.clickX - x) > mouse.DRAG_THRESHOLD ||
Math.abs(mouse.clickY - y) > mouse.DRAG_THRESHOLD)
) {
mouse.isDragging = true;
}
});
$("#card").on("touchend", (e) => {
let x = e.touches[0].clientX - $("#card").offset().left;
let y = e.touches[0].clientY - $("#card").offset().top;
mouse.x = x;
mouse.y = y;
mouse.clickX = -1;
mouse.clickY = -1;
mouse.isDragging = false;
mouse.isClicked = false;
});
arc = {
x: 250,
y: canvas.height / 2,
radius: 200,
width: 16
};
for (let i = 0; i < NUM_OF_PARTICLES; i++) {
particles[i] = {
id: i,
x: arc.x + 10,
y: arc.y + 4 * i,
velocity: 1,
maxVelocity: 5,
minVelocity: 0.2,
dx: 1,
dy: 0,
radius: 15,
energy: 0.5
};
}
let loop = function () {
update();
draw();
};
let interval = setInterval(loop, 10);
draw();
});
function update() {
if (
mouse.isDragging &&
isWithin(
mouse.clickX,
mouse.clickY,
scaleControl.x,
scaleControl.handleY,
scaleControl.width,
scaleControl.handleHeight
)
) {
scaleDragged = true;
}
if (!mouse.isDragging && scaleDragged) {
scaleDragged = false;
}
if (scaleDragged) {
scaleControl.handleY = bound(
mouse.y - scaleControl.handleHeight / 2,
scaleControl.y,
scaleControl.y + scaleControl.barHeight - scaleControl.handleHeight
);
scale =
1 -
(scaleControl.handleY - scaleControl.y) /
(scaleControl.barHeight - scaleControl.handleHeight);
} else if (!mouse.isClicked) {
//scale += 0.001;
}
if (scale > 1) {
scale = 1;
} else if (scale < 0) {
scale = 0;
}
scaleControl.handleY =
scaleControl.y +
(scaleControl.barHeight - scaleControl.handleHeight) * (1 - scale);
let collisionFound = false;
for (let i = 0; i < particles.length; i++) {
let particle = particles[i];
let old = clone(particle);
particle.velocity = particle.energy * particle.maxVelocity;
if (particle.velocity < particle.minVelocity) {
particle.velocity = particle.minVelocity;
}
particle.x += particle.velocity * particle.dx;
particle.y += particle.velocity * particle.dy;
particle.dy = particle.dy + 0.1 * (1 - particle.energy);
for (let j = 0; j < particles.length; j++) {
collisionFound = true;
let other = particles[j];
if (
other.id != particle.id &&
distance(particle.x, particle.y, other.x, other.y) <
particle.radius + other.radius
) {
let dx = particle.x - other.x;
let dy = particle.y - other.y;
let angleToCollisionPoint = Math.atan2(-dy, dx);
let oldAngle = Math.atan2(-particle.dy, particle.dx);
let newAngle = 2 * angleToCollisionPoint - oldAngle;
particle.dx = -Math.cos(newAngle);
particle.dy = Math.sin(newAngle);
let energy = (other.energy + particle.energy) / 2;
particle.energy = energy;
other.energy = energy;
particle.x = old.x;
particle.y = old.y;
while (
clearAway &&
other.id != particle.id &&
distance(particle.x, particle.y, other.x, other.y) <
particle.radius + other.radius
) {
particle.x += particle.velocity * particle.dx;
particle.y += particle.velocity * particle.dy;
}
}
}
if (
distance(particle.x, particle.y, arc.x, arc.y) >
arc.radius - arc.width - particle.radius / 2
) {
let dx = particle.x - arc.x;
let dy = particle.y - arc.y;
let angleToCollisionPoint = Math.atan2(-dy, dx);
let oldAngle = Math.atan2(-particle.dy, particle.dx);
let newAngle = 2 * angleToCollisionPoint - oldAngle;
particle.dx = -Math.cos(newAngle);
particle.dy = Math.sin(newAngle);
particle.x = old.x;
particle.y = old.y;
particle.energy = (scale + particle.energy) / 2;
}
}
if (!collisionFound) {
clearAway = false;
}
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let width = 1 / (colorArray.length - 1);
let backGradient = getGradient(
Math.floor(scale / width),
1 - (scale % width) / width
);
let frontGradient = getGradient(
Math.ceil(scale / width),
(scale % width) / width
);
drawArc(arc.x, arc.y, arc.radius, arc.width, "grey", "grey");
drawArc(
arc.x,
arc.y,
arc.radius,
arc.width,
backGradient.startColor,
backGradient.endColor
);
drawArc(
arc.x,
arc.y,
arc.radius,
arc.width,
frontGradient.startColor,
frontGradient.endColor
);
ctx.font = "100px Roboto";
ctx.textAlign = "right";
ctx.fillStyle = getGradient(
Math.floor(scale / width),
1 - (scale % width) / width
).startColor;
ctx.fillText(Math.round(scale * 150 - 25) + "°", 630, 100);
ctx.fillStyle = getGradient(
Math.ceil(scale / width),
(scale % width) / width
).startColor;
ctx.fillText(Math.round(scale * 150 - 25) + "°", 630, 100);
ctx.beginPath();
ctx.rect(
scaleControl.x,
scaleControl.y,
scaleControl.width,
scaleControl.barHeight
);
ctx.fillStyle = "rgba(76, 76, 76, 0.25)";
ctx.fill();
ctx.shadowBlur = 12 * scale;
ctx.shadowColor = "rgb(251, 42, 100)";
ctx.beginPath();
ctx.rect(
scaleControl.x,
scaleControl.handleY,
scaleControl.width,
scaleControl.handleHeight
);
ctx.fillStyle = "rgb(251, 42, 100)";
ctx.fill();
ctx.shadowBlur = 0;
ctx.save();
ctx.font = "25px Raleway";
ctx.textAlign = "center";
ctx.textBaseline = "bottom";
ctx.fillStyle = "#525454FF";
ctx.translate(
scaleControl.x + scaleControl.width,
scaleControl.y + scaleControl.barHeight / 2
);
ctx.rotate(Math.PI / 2);
ctx.fillText("Temperature", 0, 0);
ctx.restore();
for (let i = 0; i < particles.length; i++) {
let particle = particles[i];
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = getGradient(
Math.floor(particle.energy / width),
1 - (particle.energy % width) / width
).endColor;
ctx.fill();
ctx.fillStyle = getGradient(
Math.ceil(particle.energy / width),
(particle.energy % width) / width
).startColor;
ctx.fill();
}
}
function drawArc(x, y, radius, lineWidth, startColor, endColor) {
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
let gradient = ctx.createLinearGradient(
0,
canvas.height / 5,
0,
(canvas.height / 4) * 3
);
gradient.addColorStop("0", startColor);
gradient.addColorStop("1", endColor);
ctx.strokeStyle = gradient;
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.stroke();
}
function getGradient(colorArrayIndex, transparency) {
let gradient = {
startColor: colorArray[colorArrayIndex].startColor.replace(
"1.0",
transparency + ""
),
endColor: colorArray[colorArrayIndex].endColor.replace(
"1.0",
transparency + ""
)
};
return gradient;
}
function isWithin(pointX, pointY, rectX, rectY, rectWidth, rectHeight) {
if (
pointX >= rectX &&
pointX - rectX <= rectWidth &&
pointY >= rectY &&
pointY - rectY <= rectHeight
) {
return true;
}
return false;
}
function bound(num, min, max) {
if (num < min) {
num = min;
}
if (num > max) {
num = max;
}
return num;
}
function clone(object) {
return jQuery.extend({}, object);
}
function distance(x1, y1, x2, y2) {
return Math.hypot(x2 - x1, y2 - y1);
}
This Pen doesn't use any external CSS resources.