How to create circular progress(pie chart) like indicator - javascript

I have to show progress graphs exactly in following way where percentage would be in center of circular graph
How can i do this using javascript/jQuery?
Can it be done using Google Chart?

There's a plugin for this at:
http://anthonyterrien.com/knob/
Demo
jQuery Knob
canvas based ; no png or jpg sprites.
touch, mouse and mousewheel, keyboard events implemented.
downward compatible ; overloads an input element...

I searched and know there are at least 5 ways to create Circular progress indicator:
They include:
jquery.polartimer.js
jQuery Knob
CSS3 pie graph timer with jquery
circular progressBar by jQuery andCSS3
ProgressBar.js

I would recommend Highcharts JS for all of your JavaScript graphing needs
Browse through more of the demos; I think you're looking for the Donut Chart :)

You can use CSS sprites (google) for this purpose, if you want to show multiples of 10 (0%, 10%, 20% etc). I guess you need to be a graphics guru to create the sprite..
The sprite is one image containing more than one image. For your purpose, you can create an image, say 16x160px. Imagine that this image is divided into ten 16x16px "slices" and draw the corresponding percentage on that slice. You can then use CSS and JavaScript to show one "frame" from this sprite.

If you are not targeting old browsers, you can easily do that by drawing on a canvas element. This gives you the freedom to do whatever you need with the chart.
That means this solution's only requirement is jQuery and any browser that supports the canvas element...IE9+
Here's a code snippet that demonstrates it...
//input
var dimens = 256;
var color = "rgba(54, 162, 235, 0.9)";
var padding = 12;
var width = 10;
var value = 80;
var maxValue = 100;
var countFontRatio = 0.25; //ratio in relation to the dimens value
$(function() {
$(".chart-total").each(function(idx, element) {
var _render = function() {
var startingPoint = -0.5;
var pointValue = startingPoint;
var currentPoint = startingPoint;
var timer;
var _ctx;
var $canvas = $(element).find("canvas");
var canvas = $canvas.get(0);
pointValue = (value / (maxValue / 20) * 0.1) - 0.5;
canvas.height = dimens;
canvas.width = dimens;
if (!countFontRatio)
$canvas.parent().find(".legend-val").css("font-size", dimens / value.toString().length);
else
$canvas.parent().find(".legend-val").css("font-size", dimens * countFontRatio);
_ctx = canvas.getContext("2d");
var _draw = function() {
_ctx.clearRect(0, 0, dimens, dimens);
_ctx.beginPath();
_ctx.arc(dimens / 2, dimens / 2, (dimens / 2) - padding, startingPoint * Math.PI, 1.5 * Math.PI);
_ctx.strokeStyle = "#ddd";
_ctx.lineWidth = 2;
_ctx.lineCap = "square";
_ctx.stroke();
_ctx.beginPath();
_ctx.arc(dimens / 2, dimens / 2, (dimens / 2) - padding, startingPoint * Math.PI, currentPoint * Math.PI);
_ctx.strokeStyle = color;
_ctx.lineWidth = width;
_ctx.lineCap = "round";
_ctx.stroke();
currentPoint += 0.1;
if (currentPoint > pointValue) {
clearInterval(timer)
}
};
timer = setInterval(_draw, 100);
};
_render();
$(window).resize(function() {
_render();
});
});
})
body {
font-family: 'Open Sans', sans-serif;
color: #757575;
}
.chart-total {
position: relative;
margin: 0 auto;
}
.chart-total-legend {
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translateY(-50%) translateX(-50%);
-o-transform: translateY(-50%) translateX(-50%);
-moz-transform: translateY(-50%) translateX(-50%);
-moz-transform: translateY(-50%) translateX(-50%);
transform: translateY(-50%) translateX(-50%);
}
.legend-val {
font-size: 4em;
display: block;
text-align: center;
font-weight: 300;
font-family: 'Roboto', sans-serif;
}
.legend-desc {
display: block;
margin-top: 5px;
text-align: center;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto:300,400" rel="stylesheet">
<div class="chart-total" style="max-width: 256px;">
<canvas height="256" width="256"></canvas>
<div class="chart-total-legend">
<span class="legend-val" value="3933" style="font-size: 64px;">3,933</span>
<span class="legend-desc">Total Progress</span></div>
</div>

I don't think you can do it with javascript/jquery/css alone. You need to render different images, for each step one and display the proper one.
It could be made with flash (probably there are ready made components) or with svg or html5 canvas element or an api which uses one of the above backends.

Related

OpenType JS and FabricJS text convert to path

I'm using FabricJS to allow a user to design an SVG in the browser. When I'm looking to save I'm trying to use OpenType JS to convert the textbox (Fabric) into an SVG Path using OpenType.
Problem I'm seeing is the location of my textbox is not translating through to the new path addition to the canvas.
AND
When I add the new path to the canvas, then call toSVG() it disappears in the resulting SVG I save.
Code:
async function convertTextToPaths() {
ungroup();
var _all = canvas.getObjects();
for(i=0;i<_all.length;i++) {
var activeObject = _all[i];
if(activeObject.type=="textbox") {
const font = await opentype.load('fonts/'+activeObject.fontFamily+'.ttf');
debugger;
console.log(activeObject.type, activeObject.left, activeObject.top+activeObject.height, activeObject.fontSize);
const path = font.getPath(activeObject.text, activeObject.left, activeObject.top+activeObject.height, activeObject.fontSize);
const outlinetextpath = new fabric.Path(path.toPathData(3));
activeObject.dirty=true;
canvas.remove(activeObject);
canvas.insertAt(outlinetextpath,2);
canvas.renderAll();
}
}
}
Make any sense or can someone share some thoughts?
thank you
Fabric.js generated boundingBoxes differ from those generated by opentype.js
You need to calculate some scaling factors/ratios according to your font's metrics for appropriate vertical alignments.
Calling font.getPath(string, x, y, fontSize) will "draw" a path from bottom to top:
Example: draw text element at x=500, y=250
(canvas size: 1000×500px; font-family: Fira Sans; font-size: 100px;)
fabric.js
let activeObject = new fabric.Textbox('Hamburg', {
left: 500,
top: 250,
fontFamily: 'Fira Sans',
fontSize: 100
});
opentype.js
font.getPath('Hamburg', 500, 250, 100)
Red: opentype.js path; Black: fabric generated textBox
Left: top: 250
Right: object.top+object.height
The opentype.js generated element is vertically aligned to 250 px using the font's baseline as a reference point.
Whereas fabric.js aligns the textBox element according to it's top (boundary box) y coordinate.
Working example
(Download function won't work on SO due to content security policies)
const canvas = new fabric.Canvas("canvas");
const fontFileUrl = 'https://fonts.gstatic.com/s/firasans/v16/va9E4kDNxMZdWfMOD5Vvl4jO.ttf';
const [textBoxX, textBoxY] = [500, 250];
const fontName = "Fira Sans";
const fontWeight = 400;
const fontSizeCanvas = 100;
const textboxString = "Hamburg";
const btnDownload = document.querySelector('.btn-download')
convertTextToPaths();
async function convertTextToPaths() {
//parse font file with opentype.js
const font = await opentype.load(fontFileUrl);
//draw textbox on canvas
let activeObject = new fabric.Textbox(textboxString, {
left: textBoxX,
top: textBoxY,
fontFamily: fontName,
fontWeight: fontWeight,
fontSize: fontSizeCanvas
});
canvas.add(activeObject);
// get properties of fabric.js object
let [type, string, fontSize] = [activeObject.type, activeObject.text, activeObject.fontSize];
let [left, top, height, width] = [activeObject.left, activeObject.top, activeObject.height, activeObject
.width
];
/**
* Get metrics and ratios from font
* to calculate absolute offset values according to font size
*/
let unitsPerEm = font.unitsPerEm;
let ratio = fontSize / unitsPerEm;
// font.descender is a negative value - hence Math.abs()
let [ascender, descender] = [font.ascender, Math.abs(font.descender)];
let ascenderAbs = Math.ceil(ascender * ratio);
let descenderAbs = Math.ceil(descender * ratio);
let lineHeight = (ascender + descender) * ratio;
/**
* calculate difference between font path bounding box and
* canvas bbox (including line height)
*/
let font2CanvasRatio = 1 / lineHeight * height
let baselineY = top + ascenderAbs * font2CanvasRatio;
// Create path object from font
path = font.getPath(string, left, baselineY, fontSize);
//path = font.getPath(string, left, top, fontSize);
let pathData = path.toPathData();
// render on canvas
const outlinetextpath = new fabric.Path(pathData, {
fill: 'red'
});
canvas.add(outlinetextpath);
//optional: just for illustration: render center and baseline
canvas.add(new fabric.Line([0, 250, 1000, 250], {
stroke: 'red'
}));
canvas.add(new fabric.Line([0, baselineY, 1000, baselineY], {
stroke: 'purple'
}));
// Download/export svg
upDateSVGExport(canvas);
canvas.on('object:modified', function(e) {
//console.log('changed')
upDateSVGExport(canvas);
});
}
function upDateSVGExport(canvas) {
let svgOut = canvas.toSVG();
let svgbase64 = 'data:image/svg+xml;base64,' + btoa(svgOut);
btnDownload.href = svgbase64;
}
#font-face {
font-family: "Fira Sans";
font-style: normal;
font-weight: 400;
src: url(https://fonts.gstatic.com/s/firasans/v16/va9E4kDNxMZdWfMOD5Vvl4jO.ttf) format("truetype");
}
* {
box-sizing: border-box;
}
body {
font-family: "Fira Sans";
font-weight: 400;
background: #000;
margin: 0;
padding: 1em;
}
.fabricContainer {
display: block;
width: 100%;
height: auto;
max-width: 100%!important;
position: relative;
background: #fff;
aspect-ratio: 2/1;
}
.canvas-container {
width: 100%!important;
height: auto!important;
}
.canvas-container>canvas {
height: auto !important;
max-width: 100%;
}
canvas {
display: block;
width: 100% !important;
font-family: inherit;
}
h3 {
font-weight: 400
}
.btn-download {
text-decoration: none;
background: #777;
color: #fff;
padding: 0.3em;
position: absolute;
width: auto !important;
bottom: 0.5em;
right: 0.5em;
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/opentype.js#latest/dist/opentype.min.js"></script>
<div class="fabricContainer" id="fabricContainer">
<canvas id="canvas" width="1000" height="500"></canvas>
<a class="btn-download" href="#" download="fabric2Svg.svg">Download svg</a>
</div>
Codepen example
How it works:
Essentially, we need to compare heights as rendered by fabrics.js testBox objects with ideally rendered boundaries based on font metrics.
First we need to get some ratios to translate font units to pixels.
Most importantly we need to calculate a ratio/factor to translate relative font metric values to absolute font size related pixel values:
1. Font metrics: font size to font unit ratio
let unitsPerEm = font.unitsPerEm;
let ratio = fontSize / unitsPerEm;
Most webfonts have a 1000 unitsPerEm value.
However, traditional truetype fonts (so not particularly optimised for web usage) usually use 2048 units per em.
2. Font metrics: ascender and descender
// font.descender is a negative value - hence Math.abs()
let [ascender, descender] = [font.ascender, Math.abs(font.descender)];
let ascenderAbs = Math.ceil(ascender * ratio);
let descenderAbs = Math.ceil(descender * ratio);
let lineHeight = (ascender + descender) * ratio;
With regards to the font's metrics an ideal bBox would have the height of
(ascender + descender) * FontSize2unitsPerEmRatio.
3. Font metrics to canvas coordinates
Fabric.js bBox is slightly bigger – so we need to compare their heights to get the perfect scaling factor.
let font2CanvasRatio = 1 / lineHeight * height
Now we get the right y offset when using getPath()
let baselineY = top + ascenderAbs * font2CanvasRatio;
path = font.getPath(string, left, baselineY, fontSize);

Generate a normal map with a color map in Three js

I started learning Three js and I was looking for a way to convert a color map into a normal map. What I want to do is to try and make the normal map based on this color map [image 1], by changing the pixels based on their color so it looks like this normal map [image 2]. I don't want to simply upload the files since I'm trying to minimize the weight of the project as much as possible. Here is what I already tried :
let img = new Image();
img.src = './texture/color.jpg';
img.onload = function () {
let canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
document.getElementById('body').appendChild(canvas)
const c = canvas.getContext('2d')
c.clearRect(0, 0, canvas.width, canvas.height);
c.fillStyle = '#EEEEEE';
c.fillRect(0,0,canvas.width, canvas.height);
//draw background image
c.drawImage(img, 0, 0);
//draw a box over the top
c.fillStyle = "rgba(200, 0, 0, 0)";
c.fillRect(0, 0, canvas.width, canvas.height);
draw(c, canvas);
};
function draw(c, canvas)
{
let img2 = c.getImageData(0, 0, canvas.width,canvas.height);
console.log(img2.data)
let d = img2.data;
for (let i=0; i<d.length; i+=4) {
let r = d[i];
let g = d[i+1];
let b = d[i+2];
v1 = r < 75 ? r / (50 - r) : r * (255 - r);
v2 = g > 75 ? g / (50 - g) : g * (255 - g);
v3 = b > 75 ? b / (50 - b) : b * (255 - b);
d[i] = v1;
d[i+1] = v2;
d[i+2] = v3;
}
console.log(img2.data)
c.putImageData(img2, 0, 0);
}
I can't say what Three.js can or can't do because all I really know of it is that it makes integrating 3d assets with canvases a breeze.
Aside from that, I wrote a pure javascript function that serves the purpose of generating normal maps from color maps quite effectively. Keep in mind, however, that this is a quick port to js of a function I wrote for C# winforms about 4 years ago, one that loops through all the pixels of a given image to extrapolate the data required for the conversion. It's slow. Seriously, painfully slow and it is so because getting nice, crisp, accurate normal maps from recursive algorithms is painfully slow.
But it does exactly what you want it to do; generate a very nice, clean, precise normal map from a given color map and it's simple enough to understand its functionality.
I've set this up as a live demo so you can see it / feel it in action.
There is, of course, a much faster solution involving making a single call for pixel data, iterating over its corresponding 1d array, saving calculated data back to that array then plopping the entire array, itself, down on the output canvas all at once but that involves some interesting virtual multi-dimensional trickery for Sobel but, for the sake of providing a clear, understandable example that works, I'm going with old-school nested recursion so you can see Sobel in action and how pixels are manipulated to arrive at normalization.
I did not implement any fancy asynchronous updates so you'll only know this is processing because, once initiated, the hand cursor used for the button won't return to the default arrow until map generation is complete.
I've also included 4 variations of your original image to play with, all in code with 3 of the 4 commented out. The app starts at 256x256 as the time it takes to generate a normal map from that with recursion is reasonable. Their sizes range from 128 to the original 1024, though I highly advise not engaging the full scale variant as your browser may whine about how long the operation takes.
As with the C# variant, you can implement a means by which client can control the intensity of the resulting normal calculations by adjusting the brightness parameters. Beyond the C# variant, this definitely can be a basis for generating normal maps to visualize in real-time as applied to geometry with Three.js. And by "real-time", I mean however long it takes to generate an x-scale map with nested recursion because the actual application of a completed map to geometry occurs in milliseconds.
Here's a screenshot of the results after processing the 256x256:
To accompany the live demo, here's the code:
normalize.htm
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Normalizer</title>
<link rel="stylesheet" href="css/normalize.css">
</head>
<body onload="startup()">
<canvas id="input" class="canvas"></canvas>
<canvas id="output" class="canvas"></canvas>
<div class="progress">
<input type="button" class="button" onclick="totallyNormal()" value="Normalize!" />
<span id="progress">Ready to rock and / or roll on your command!</span>
</div>
<script src="js/normalize.js"></script>
</body>
</html>
normalize.css
html {
margin: 0px;
padding: 0px;
width: 100vw;
height: 100vh;
}
body {
margin: 0px;
padding: 0px;
width: 100vw;
height: 100vh;
overflow: hidden;
font-family: "Arial Unicode MS";
background: linear-gradient(330deg, rgb(150, 150, 150), rgb(200, 200, 200));
background-color: rgb(200, 200, 200);
display: flex;
align-items: center;
justify-content: center;
}
.canvas {
outline: 1px solid hsla(0, 0%, 0%, 0.25);
}
.progress {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 40px;
display: flex;
}
.progress span {
width: calc(100% - 160px);
height: 40px;
line-height: 40px;
color: hsl(0, 0%, 0%);
text-align: center;
}
input[type="button"] {
margin: 0px;
width: 120px;
height: 40px;
cursor: pointer;
display: inline;
}
normalize.js
// Javascript Normal Map Generator
// Copyright © Brian "BJS3D" Spencer 2022
// Incorporating W3C proposed algorithm to
// calculate pixel brightness in conjunction
// with the Sobel Operator.
var input, output, ctx_i, ctx_o, w, h;
function startup() {
var img;
input = document.getElementById("input");
ctx_i = input.getContext("2d");
ctx_i.clearRect(0, 0,input.width, input.height);
img = new Image();
img.crossOrigin = "Anonymous";
//img.src = "https://i.imgur.com/a4N2Aj4.jpg"; //128x128 - Tiny but fast.
img.src = "https://i.imgur.com/wFe4EG7.jpg"; //256x256 - Takes about a minute.
//img.src = "https://i.imgur.com/bm4pXrn.jpg"; //512x512 - May take 5 or 10 minutes.
//img.src = "https://i.imgur.com/aUIdxHH.jpg"; //original - Don't do it! It'll take hours.
img.onload = function () {
w = img.width - 1;
h = img.height - 1;
input.width = w + 1;
input.height = h + 1;
ctx_i.drawImage(img, 0, 0);
output = document.getElementById("output");
ctx_o = output.getContext("2d");
output.width = w + 1;
output.height = h + 1;
};
}
function totallyNormal() {
var pixel, x_vector, y_vector;
for (var y = 0; y < w + 1; y += 1) {
for (var x = 0; x < h + 1; x += 1) {
var data = [0, 0, 0, 0, x > 0, x < w, y > 1, y < h, x - 1, x + 1, x, x, y, y, y - 1, y + 1];
for (var z = 0; z < 4; z +=1) {
if (data[z + 4]) {
pixel = ctx_i.getImageData(data[z + 8], data[z + 12], 1, 1);
data[z] = ((0.299 * (pixel.data[0] / 100)) + (0.587 * (pixel.data[1] / 100)) + (0.114 * (pixel.data[2] / 100)) / 3);
} else {
pixel = ctx_i.getImageData(x, y, 1, 1);
data[z] = ((0.299 * (pixel.data[0] / 100)) + (0.587 * (pixel.data[1] / 100)) + (0.114 * (pixel.data[2] / 100)) / 3);
}
}
x_vector = parseFloat((Math.abs(data[0] - data[1]) + 1) * 0.5) * 255;
y_vector = parseFloat((Math.abs(data[2] - data[3]) + 1) * 0.5) * 255;
ctx_o.fillStyle = "rgba(" + x_vector + "," + y_vector + ",255,255)";
ctx_o.fillRect(x, y, 1, 1);
}
}
document.getElementById("progress").innerHTML = "Normal map generation complete.";
}

How to animate spining wheel in CSS?

I don't know how to form the question. I have old animation that was done in Flash, that I'm not able to translate to CSS animation.
This is an animation of a Spinning Wheel.
The question is: How can I implement the CSS animation for the crank/handle that drives the wheel? It should work like a Piston but connected to a circe in id="rotator" (inside SVG). The handle is located at 0,0 on the image above. Both small circles should match when the rotator is rotating. I already tried to use translate, and used transform-origin but I have no idea how to implement the animation. I have Action Script code as a reference, but I don't know how to map it to CSS or SCSS.
I was able to convert SWF into SVG, using pyswf. And I have a hard time understanding the math behind the logic I've created long ago and translating it into CSS (I've also tried with JS approach).
The code in Action Script I've extracted long ago, probably some decompiler from the swf file.
// copyright (c) 2006 Jakub "JCubic" Jankiewicz
var
factor1 = 180.0 / Math.PI, // współczynniki dla zamiany
factor2 = Math.PI / 180.0; // radiany stopinie i odwrotnie
// radiany na katy
function rad2deg(rad){
return rad * factor1;
}
// katy na radiany
function deg2rad(deg){
return deg * factor2;
}
// dlugosc odcinka
function line_length(x1, y1, x2, y2){
return Math.sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
}
// funkcja testowa przenoszaca krzyzyk
function show(x, y){ cross._x = x; cross._y = y; }
// funkcja testowa rysująca linie
function line_(x1, y1, x2, y2, ang){
line._height = Math.sqrt((x2 - x1)*(x2 - x1) + (y2 - y1)*(y2 - y1));
line._x = (x2 + x1) / 2;
line._y = (y2 + y1) / 2;
line._rotation = ang;
}
function getSpeed(){
return step;
}
function setSpeed(value){
// inicjacja zmiennych
var Ox = 0,
Oy = 360, // punkt zaczepienia
r = 83.0, // promien odleglosc od rotatora
angle = 0,
step = value,
rad = deg2rad(step), // cosinnus i sinus stopnia
sinA = Math.sin(rad), // jednostkowego
cosA = Math.cos(rad),
sinAlpha;
onEnterFrame = function() {
if (angle == 0) {
napedzacz._x = 0; // poczatek obrotu
napedzacz._y = r;
} else {
Bx = napedzacz._x * cosA + napedzacz._y * sinA;
By = napedzacz._y * cosA - napedzacz._x * sinA;
napedzacz._x = Bx;
napedzacz._y = By;
sinAlpha = (Ox - Bx) / line_length(Bx, By, Ox, Oy);
napedzacz._rotation = - rad2deg(Math.asin(sinAlpha));
kolo._rotation = 180 - angle;
szpulka._rotation = 180 - angle * 2;
rotator._rotation = 180 - angle;
}
angle = (angle+step) % 360;
}
}
It has Polish comments but they don't explain how to modify the code.
Here is my CodePen demo where I've tried to create SVG+CSS animation. I was able to rotate part of the spinning wheel, but don't know how to animate the handle (that is used to drive the wheel).
I've wanted to use SCSS and trigonometry functions (included in Pen) to generate every frame of the animation, but I'm not sure how I should go about it.
I have the original SWF file but I'm not able to play it, since the only SWF player I've found doesn't execute code. And I'm not able to install Gnash on Fedora (even that I've written article how to do that long go, the solution doesn't work anymore). That's why I want to create something modern with SVG.
If you have something to open the SWF file here is the link to the original file:
https://jcubic.pl/kolowrotek.swf
the problem is that constants in the Action Script code use different coordinate systems and different scales, I have no idea how to map that code into JavaScript (I was able to run it, but it has issues, there are two commented-out lines in Pen). I also have no idea why the animation rotate in different direction than CSS, I was not able to reverse it. I would prefer not to use JavaScript since it will hard to match CSS animation with JavaScript, and doing aninmation in JavaScript make some delay when handle is in wrong position.
This is a basic implementation of the movement that you want.
I have set only keyframes at every 90 deg , it will get better as you add values.
I have guessed this values, bout you should calculate them and set the appropiate values in the counter rotation.
#wheel {
width: 300px;
height: 300px;
border-radius: 100%;
border: solid 1px blue;
animation: rotate 15s infinite linear;
}
#dot {
width: 5px;
height: 5px;
border-radius: 100%;
border: solid 1px red;
position: absolute;
top: 2px;
left: 150px;
}
#arm {
width: 500px;
height: 5px;
border: solid 1px green;
position: absolute;
top: 2px;
left: 150px;
transform-origin: 0px 0px;
animation: crotate 15s infinite linear;
}
#keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
#keyframes crotate {
0% {
transform: rotate(15deg);
}
25% {
transform: rotate(-90deg);
}
50% {
transform: rotate(-195deg);
}
75% {
transform: rotate(-270deg);
}
99.99% {
transform: rotate(-345deg);
}
100% {
transform: rotate(15deg);
}
}
<div id="wheel">
<div id="dot"></div>
<div id="arm"></div>
</div>
Note, following answer content may be subject to edits in the near future, and is only intended as a starting point to assist the OP's author.
I believe what ya may need for turning the crank is the transform-origin CSS property.
In regards to rotate() direction, the StackExchange -- How to determine direction of rotation in CSS3 transitions? answers may be of use. TLDR; positive numbers rotate clockwise, and negative numbers rotate anticlockwise, however this may be modified via from configuration.
I'll dig into the source a bit over the next few minutes and attempt a more complete answer.
Updates
You were very close to a solution! From what I spotted the #handle element was missing an animation property/value, and the #keyframes handle_rotate needed defined.
Here's a bit of code that should get ya a little closer to a full solution...
:root {
--time: 5s;
--handle-translate-x: 291.59718px;
--handle-translate-y: 210.10511px;
--handle-rotate-start: 20deg;
--handle-transform-origin-x: 4.742px;
--handle-transform-origin-y: 15.799px;
}
/* ... */
#handle {
animation: handle_rotate var(--time) linear infinite;
transform-origin: var(--handle-transform-origin-x) var(--handle-transform-origin-y);
transform:
translate(var(--handle-translate-x), var(--handle-translate-y))
rotate(var(--handle-rotate-start));
}
/* ... */
#keyframes handle_rotate {
to {
transform:
translate(var(--handle-translate-x), var(--handle-translate-y))
rotate(calc(var(--handle-rotate-start) + 360deg));
}
}
... Though be aware values still need a bit of fiddling with to get all the parts to sync-up with one another. In this case I'd suggest leveraging CSS Custom Properties (variables)

Why doesn't the following element rotate?

I have the following code that aims to rotate a hamburger icon by 90 deg when it is clicked, but it doesn't work why?
var menu = document.getElementById("menu");
let rotate = function() {
document.getElementById("menu").style.transform = "rotate(90deg)";
};
menu.addEventListener("click", rotate);
#menu {
text-decoration: none;
color: white;
font-size: 5vw;
color: #66fcf1;
cursor: default;
position: fixed;
margin-left: 2.5%;
}
☰
Also on Codepen: https://codepen.io/greatscams/pen/ZEQrYog
Note: It only works once.
You need to check if the element is already rotated or not. It does not happen like just rotating the paper. rotate(90deg) means you are assigning the property and not actually rotating the element.
var rotated = false;
let rotate = function () {
if(rotated) {
document.getElementById("menu").style.transform = "rotate(0deg)";
rotated = false;
} else {
document.getElementById("menu").style.transform = "rotate(90deg)";
rotated = true;
}
};
It's only rotating once as it's being rotated by 90deg, not an additional 90deg each time.
To keep rotating each time the function runs you need something along the lines of this:
let deg = 0
let rotate = function () {
deg = deg + 90;
document.getElementById("menu").style.transform = `rotate(${deg}deg)`
};
So the first invocation will be rotate(90deg) the next rotate(180deg) etc
The demo works only once because you're applying the same transform function over and over again.
The first state of the element is transform: none.
When first clicking on it, you're switching it to transform: rotate(90deg).
The transform works relatively to the initial state, not the current one (it's actually stateless), so the code above will not rotate it again and again.
What you can do, depending on what you want, is:
remember the state and toggle the animation (from rotate(0) to rotate(90deg) and back)
or
count the number of rotations and then multiply 90deg by that number on every click.
It only works once because the second time, you're just telling it to transform the way it's already transformed (rotated 90deg). To rotate it further, you have to tell it to rotate further. It's not additive.
For instance, extracting the angle with a regex and adding 90 to it each time, with wrap-around at 360:
let rotate = function() {
const menuStyle = document.getElementById("menu").style;
const current = /rotate\((\d+)deg\)/.exec(menuStyle.transform);
let angle = current ? +current[1] : 0;
angle = angle + 90 % 360;
menuStyle.transform = `rotate(${angle}deg)`;
};
menu.addEventListener("click", rotate);
#menu {
text-decoration: none;
color: white;
font-size: 5vw;
color: #66fcf1;
cursor: default;
position: fixed;
margin-left: 2.5%;
}
☰

How to set an element's background as the same as a specific portion of web page

Intro:
I have a sticky header and a body which has linear gradient.
Goal:
I'd like to set the header's background as the same of a specific area, that is to say where it initially sits on the top.
Solution tried:
Using the dev tool color picker to find the first value on the top and the second value where the header ends.
Result:
this works.
In this way the header looks like is integrated and part of the body's linear gradient.
It maintains its background as I scroll down the page.
Issue:
If the page's height changes the body's linear gradient will be recalculated indeed.
So now the header's background need a recalculation as well.
As I am new to programming I would appreciate any suggestion that can help me to solve this dynamically.
Guess it's gonna be Javascript helping out.
Here I found a user with the same issue.
Thanks a lot for your time guys!
Instead of using a CSS background gradient, you can create a canvas with z-index -1 and the same size of your page. In the canvas you can render your gradient. This has the advantage, that you can query the canvas for the color at a specific position, which is not possible with the CSS background gradient. By this you can update the background color of your header, whenever a resize or scroll event occurs.
var canvas = document.getElementById ('background');
var ctx = canvas.getContext ('2d');
var header = document.getElementById ('header');
function scroll()
{
var y = window.scrollY + header.getClientRects()[0].height;
var rgba = ctx.getImageData (0, y, 1, 1).data;
header.style.backgroundColor = 'rgba(' + rgba.join(',') + ')';
}
function draw()
{
var colors = ['red', 'orange', 'yellow', 'green',
'blue', 'indigo', 'violet'];
var gradient = ctx.createLinearGradient (0, 0, 0, canvas.height);
for (var i=0; i < colors.length; i++) {
gradient.addColorStop (i/(colors.length-1), colors[i]);
}
ctx.fillStyle = gradient;
ctx.fillRect (0, 0, canvas.width, canvas.height);
scroll();
}
function resize()
{
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
draw();
}
window.addEventListener('resize', resize, false);
window.addEventListener('scroll', scroll, false);
resize();
body {
height: 100rem;
overflow: scroll;
margin: 0;
}
canvas {
display: block;
height: 100%;
width: 100%;
z-index: -1;
margin: 0;
}
#header {
position: fixed;
top: 0;
left: 50%;
right: 0;
height: 50%;
border-bottom: 1pt solid white;
}
<body>
<canvas id="background"></canvas>
<div id="header">
Header
</div>
<script src="gradient.js"></script>
</body>

Categories

Resources