Skip to content

Instantly share code, notes, and snippets.

@kddlb
Created July 11, 2018 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kddlb/80f780ddbb78f6fe42f70d91f1656336 to your computer and use it in GitHub Desktop.
Save kddlb/80f780ddbb78f6fe42f70d91f1656336 to your computer and use it in GitHub Desktop.
Procedurally generated cursor // source https://jsbin.com/huhomen
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Procedurally generated cursor</title>
<style>
td:first-child, td:last-child {
text-align: right;
}
td:last-child {
font-family: monospace;
width: 3em;
}
#cursorRender {
image-rendering: pixelated;
border: 1px solid red;
}
.test {
border: 1px solid blue;
width: 75vh;
height: 10em;
padding: 1em;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
}
.testArea {
}
</style>
</head>
<body>
<canvas id="cursorRender" width="128" height="256"></canvas>
<form>
<table>
<tbody>
<tr>
<td><strong>Rotation</strong></td>
<td><input type="range" min="0" max="360" id="rotation" value="0"></td>
<td id="rotationDisplay">0&deg;</td>
</tr>
</tbody>
</table>
<div class="test">
<div class="testArea" id="testArea" contenteditable="true">
Test
</div>
</div>
</form>
<script>
let canvas = document.getElementById("cursorRender");
let context = canvas.getContext("2d");
let scale = 2;
let rotation = 0;
let gWidth = 21;
let gHeight = 20;
let roundRect = (ctx, x, y, width, height, radius, fill, stroke) => {
if (typeof stroke == 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
};
let renderCursor = (angle, context, scaleFrom16) => {
const s = scaleFrom16;
const o = s * 2;
context.fillStyle = "rgb(0,0,0)";
context.strokeStyle = "black";
context.lineWidth = s * 1;
roundRect(context, s * -8, o * -3.25, s * 8, s * 13, {tl: 0, tr: s * 2, bl: 0, br: s * 2}, false, true);
roundRect(context, 0, o * -3.25, s * 8, s * 13, {tl: s * 2, tr: 0, bl: s * 2, br: 0}, false, true);
context.moveTo(s * -2.5, s * 0.5);
context.lineTo(s * 2.5, s * 0.5);
context.stroke();
context.clearRect(s*-9.5, -canvas.height, s * 5, canvas.height*2);
context.clearRect(s *4.5, -canvas.height, s * 5, canvas.height*2);
};
canvas.width = scale * gWidth;
canvas.height = scale * gHeight;
let renderEverything = () => {
let widthPixels = [...Array(canvas.width / scale).keys()];
let heightPixels = [...Array(canvas.height / scale).keys()];
context.clearRect(-canvas.width, -canvas.height, canvas.width*2, canvas.height*2);
context.resetTransform();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(rotation * Math.PI / 180);
renderCursor(0, context, scale);
context.lineWidth = 1;
context.strokeStyle = "red";
if (scale >= 4) {
heightPixels.forEach((value, index, array) => {
let orx = 0 - (canvas.width / 2);
let ory = (canvas.width / 2) + scale / 2 - (scale * (value + 1)) - 0.5;
let dex = canvas.width;
let dey = (canvas.width / 2) + scale / 2 - (scale * (value + 1)) + -0.5;
context.moveTo(orx, ory);
context.lineTo(dex, dey);
context.stroke();
});
widthPixels.forEach((value, index, array) => {
let orx = (canvas.width / 2) + scale - (scale * (value + 1)) + 0.5;
let ory = -canvas.height/2;
let dex = (canvas.width / 2) + scale - (scale * (value + 1)) + 0.5;
let dey = canvas.height/2;
context.moveTo(orx, ory);
context.lineTo(dex, dey);
context.stroke()
});
}
let url = canvas.toDataURL();
document.getElementById("testArea").style.cursor = `url(${url}) ${scale*10} ${scale*11}, crosshair`
};
renderEverything();
let rotationSlider = document.getElementById("rotation");
let rotationDisplay = document.getElementById("rotationDisplay");
rotationSlider.addEventListener("input", () => {
rotation = rotationSlider.value;
rotationDisplay.innerText = `${rotationSlider.value}°`;
document.getElementById("testArea").style.transform = `rotate(${rotationSlider.value}deg)`;
renderEverything();
})
</script>
<script id="jsbin-source-html" type="text/html"><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Procedurally generated cursor</title>
<style>
td:first-child, td:last-child {
text-align: right;
}
td:last-child {
font-family: monospace;
width: 3em;
}
#cursorRender {
image-rendering: pixelated;
border: 1px solid red;
}
.test {
border: 1px solid blue;
width: 75vh;
height: 10em;
padding: 1em;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
}
.testArea {
}
</style>
</head>
<body>
<canvas id="cursorRender" width="128" height="256"></canvas>
<form>
<table>
<tbody>
<tr>
<td><strong>Rotation</strong></td>
<td><input type="range" min="0" max="360" id="rotation" value="0"></td>
<td id="rotationDisplay">0&deg;</td>
</tr>
</tbody>
</table>
<div class="test">
<div class="testArea" id="testArea" contenteditable="true">
Test
</div>
</div>
</form>
<script>
let canvas = document.getElementById("cursorRender");
let context = canvas.getContext("2d");
let scale = 2;
let rotation = 0;
let gWidth = 21;
let gHeight = 20;
let roundRect = (ctx, x, y, width, height, radius, fill, stroke) => {
if (typeof stroke == 'undefined') {
stroke = true;
}
if (typeof radius === 'undefined') {
radius = 5;
}
if (typeof radius === 'number') {
radius = {tl: radius, tr: radius, br: radius, bl: radius};
} else {
var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
for (var side in defaultRadius) {
radius[side] = radius[side] || defaultRadius[side];
}
}
ctx.beginPath();
ctx.moveTo(x + radius.tl, y);
ctx.lineTo(x + width - radius.tr, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
ctx.lineTo(x + width, y + height - radius.br);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
ctx.lineTo(x + radius.bl, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
ctx.lineTo(x, y + radius.tl);
ctx.quadraticCurveTo(x, y, x + radius.tl, y);
ctx.closePath();
if (fill) {
ctx.fill();
}
if (stroke) {
ctx.stroke();
}
};
let renderCursor = (angle, context, scaleFrom16) => {
const s = scaleFrom16;
const o = s * 2;
context.fillStyle = "rgb(0,0,0)";
context.strokeStyle = "black";
context.lineWidth = s * 1;
roundRect(context, s * -8, o * -3.25, s * 8, s * 13, {tl: 0, tr: s * 2, bl: 0, br: s * 2}, false, true);
roundRect(context, 0, o * -3.25, s * 8, s * 13, {tl: s * 2, tr: 0, bl: s * 2, br: 0}, false, true);
context.moveTo(s * -2.5, s * 0.5);
context.lineTo(s * 2.5, s * 0.5);
context.stroke();
context.clearRect(s*-9.5, -canvas.height, s * 5, canvas.height*2);
context.clearRect(s *4.5, -canvas.height, s * 5, canvas.height*2);
};
canvas.width = scale * gWidth;
canvas.height = scale * gHeight;
let renderEverything = () => {
let widthPixels = [...Array(canvas.width / scale).keys()];
let heightPixels = [...Array(canvas.height / scale).keys()];
context.clearRect(-canvas.width, -canvas.height, canvas.width*2, canvas.height*2);
context.resetTransform();
context.translate(canvas.width / 2, canvas.height / 2);
context.rotate(rotation * Math.PI / 180);
renderCursor(0, context, scale);
context.lineWidth = 1;
context.strokeStyle = "red";
if (scale >= 4) {
heightPixels.forEach((value, index, array) => {
let orx = 0 - (canvas.width / 2);
let ory = (canvas.width / 2) + scale / 2 - (scale * (value + 1)) - 0.5;
let dex = canvas.width;
let dey = (canvas.width / 2) + scale / 2 - (scale * (value + 1)) + -0.5;
context.moveTo(orx, ory);
context.lineTo(dex, dey);
context.stroke();
});
widthPixels.forEach((value, index, array) => {
let orx = (canvas.width / 2) + scale - (scale * (value + 1)) + 0.5;
let ory = -canvas.height/2;
let dex = (canvas.width / 2) + scale - (scale * (value + 1)) + 0.5;
let dey = canvas.height/2;
context.moveTo(orx, ory);
context.lineTo(dex, dey);
context.stroke()
});
}
let url = canvas.toDataURL();
document.getElementById("testArea").style.cursor = `url(${url}) ${scale*10} ${scale*11}, crosshair`
};
renderEverything();
let rotationSlider = document.getElementById("rotation");
let rotationDisplay = document.getElementById("rotationDisplay");
rotationSlider.addEventListener("input", () => {
rotation = rotationSlider.value;
rotationDisplay.innerText = `${rotationSlider.value}°`;
document.getElementById("testArea").style.transform = `rotate(${rotationSlider.value}deg)`;
renderEverything();
})
<\/script>
</body>
</html>
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment