Curved text with pure JS or React.js - javascript

I want to create a curved text that will change its curvature based on the value from the input range. If the value of the input is positive, then the curvature of the text should be downwards, if the value is negative, then the curvature should be upwards, and if it is 0, then the text will not have curvature.
The written code must be without any library, canvas, svg path object. Must be pure javascript or React.js
I want to get like this result
https://www.youtube.com/watch?v=RU63sdgydPE
This code is not working properly․
HTML
const updateCurvedText = (curvedText, radius) => {
const diameter = 2 * radius
const w = curvedText.offsetWidth
const h = curvedText.offsetHeight
const L = Math.PI * radius
const text = curvedText.innerText
let html = ""
textArr = text.split("")
textArr.forEach((item) => {
html += "<span>" + item + "</span>"
})
curvedText.innerHTML = html
let letters = curvedText.querySelectorAll("span")
if (!letters) {
return
} else {
letters.forEach((letter, index) => {
const angleRad = w / (2 * radius * Math.PI)
const angle = (360 * angleRad) / (letters.length * Math.PI)
const deltaDegree = L / letters.length
let deg = index * angle - (letters.length * angle) / 2
letter.style.height = `${radius}px`
letter.style.transform = `translate( ${w / 2}px, 0px) rotate( ${deg}deg)`
curvedText.style.height = `${radius}px`
deg += deltaDegree
})
}
}
const curvedText = document.querySelector(".curved-text")
updateCurvedText(curvedText, 2000)
const settingsChanged = (value) => {
curvedText.innerText = document.querySelector(".text").value
updateCurvedText(curvedText, value)
}
const showValue = (value) => {
settingsChanged(value)
}
document.querySelector(".text").onchange = () => {
const value = document.querySelector(".radius").value
settingsChanged(value)
}
.curved-text {
display: flex;
justify-content: space-between;
border-radius: 100%;
position: relative;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
}
.curved-text span {
color: blue;
width: 0;
outline: 1px dashed gray;
position: absolute;
transform-origin:bottom center ;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./style.css" />
<title>Document</title>
</head>
<body>
<div>
<fieldset>
<label>Text:</label>
<input type="text" class="text" value="CURVED TEXT TEST" />
<label>Radius:</label>
<input
class="radius"
type="range"
step="10"
min="100"
max="2000"
value = "2000"
oninput="showValue(this.value)"
onchange="showValue(this.value)"
/>
</fieldset>
</div>
<div class="curved-text">CURVED TEXT TEST</div>
<script src="./js/new.js"></script>
</body>
</html>

Related

How to zoom an img in javascript and html

I'd like to zoom an img with a range button in javascript. However, I don't know how to control the last value the user inputs.
This is my html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Img Zoom</title>
<link rel="stylesheet" type="text/css" href="./css/style.css" />
</head>
<body>
<div id="divImg">
<img id="img" src="./img/rose.jpg" width="200px" height="200"" />
</div>
<div class="slidecontainer">
<label for="myRangeZ">Zoom</label>
<input type="range" min="1" max="100" value="20" class="slider" id="myRangeZ" onclick="jsZoom()">
</div>
</body>
<script src="./js/script.js">
</script>
</html>
and this my .js
var z, zoom;
function jsZoom() {
z = document.getElementById("myRangeZ").value;
console.log(z);
img = document.getElementById("img");
console.log(img.width);
console.log(img.height);
if (z > 20) {
zoom = 1.5;
}
else {
zoom = 0.5;
}
document.getElementById("img").style.width = (img.width * zoom) + "px";
document.getElementById("img").style.height = (img.height * zoom) + "px";
}
When the user goes from 20% to 100% the img is getting bigger, but if the range goes from 100% to 20% the img is not decreasing and it's still getting bigger. I can solve this issue with 2 bottons ( ) but I want it with a range button.
You read the width every time it changes. So you are basing the width off at that moment in time, not the time when the image was added to the page. You need to store the initial value. You can then alter the zoom code however you like to change the size based on the original dimensions.
var z, zoom;
function jsZoom() {
z = document.getElementById("myRangeZ").value;
img = document.getElementById("img");
if (!img.dataset.width) {
img.dataset.width = img.width;
img.dataset.height = img.height;
}
const width = +img.dataset.width;
const height = +img.dataset.height;
img.style.width = (width * z/100 + width) + "px";
img.style.height = (height * z/100 + height) + "px";
}
jsZoom();
img {
transition: 0.2s;
}
<div id="divImg">
<img id="img" src="https://placekitten.com/300/300" width="200px" height="200"" />
</div>
<div class="slidecontainer">
<label for="myRangeZ">Zoom</label>
<input type="range" min="1" max="100" value="20" class="slider" id="myRangeZ" oninput="jsZoom()">
Here's a simpler way:
Wrap everything in a <form>. You'll have complete control of all form controls within it.
Place <img> in a block level tag. In the example the <img> is inside a <fieldset> which is not only a block level tag, it's also a form control.
Assign the following ruleset to <img>:
img { object-fit: contain; width: 100% }
This CSS ruleset will ensure that the <img> will conform to it's container (ex. <fieldset>) and maintain aspect ratio as well.
Details are commented in example
// Bind input event on <form>
document.forms.UI.oninput = scaleFrame;
function scaleFrame(e) {
// Reference all form controls
const IO = this.elements;
/*
If the user is interacing with <input>...
...get the value of <input> and convert it into a number...
...set <fieldset> CSS transform:scale() property to the value of <input>...
...display value of <input> with <output>
*/
if (e.target.id === 'scale') {
let size = parseFloat(e.target.value);
IO.frame.style.cssText = `transform: scale(${size})`;
IO.view.value = size;
}
}
html {
font: 300 2ch/1 Consolas;
}
#frame {
width: 200px;
height: 200px;
transform-origin: top left;
}
label {
display: flex;
align-items: center;
}
img {
object-fit: contain;
width: 100%;
}
input,
output {
display: inline-block;
}
#scale {
margin-right: 1rem;
cursor: pointer;
}
<form id='UI'>
<label>
<input id='scale' type='range' min='.1' max='2.5' step='any' value='1'>
x<output id='view'>1.0</output>
</label>
<fieldset id='frame'>
<img src="https://i.ibb.co/hZj77BZ/lena01.jpg" alt="Lena Forsén (Söderberg) 1972">
</fieldset>
</form>
The image can become bigger in size after decreasing the slider because it is set to become 1.5 larger than itself every time the slider is set to a value above 20.
To fix this, simply store the original width and height of the image in a variable so the previous resizing events don't affect additional scaling.
var z, zoom;
var originalWidth = document.getElementById("img").width;
var originalHeight = document.getElementById("img").height;
function jsZoom() {
z = document.getElementById("myRangeZ").value;
img = document.getElementById("img");
if (z > 20) {
zoom = 1.5;
}
else {
zoom = 0.5;
}
document.getElementById("img").style.width = (originalWidth * zoom) + "px";
document.getElementById("img").style.height = (originalHeight * zoom) + "px";
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Img Zoom</title>
<link rel="stylesheet" type="text/css" href="./css/style.css" />
</head>
<body>
<div id="divImg">
<img id="img" src="https://cdn.commercev3.net/cdn.edmundsroses.com/images/popup/23703.jpg" width="200px" height="200"" />
</div>
<div class="slidecontainer">
<label for="myRangeZ">Zoom</label>
<input type="range" min="1" max="100" value="20" class="slider" id="myRangeZ" onclick="jsZoom()">
</div>
</body>
<script src="./js/script.js">
</script>
</html>
The problem here is that your function is using the width and height from the image, which is obviously being changed by your function. The issue should be solved by saving the initial width and height and referencing those in the last two lines of your function.
These are just "by-the-ways", but I also refactored the if/else statement and moved the width and height console.log statements to the end for the dimensions to be printed after they're updated.
var z, zoom;
var img = document.getElementById("img");
var width = img.width;
var height = img.height;
function jsZoom() {
z = document.getElementById("myRangeZ").value;
console.log(z);
zoom = z > 20 ? 1.5 : 0.5;
img.style.width = (width * zoom) + "px";
console.log(img.width);
img.style.height = (height * zoom) + "px";
console.log(img.height);
}
EDIT: The width and height variables should remain outside the function to prevent them from being updated by it and affecting the zoom as a result.

Setting strict grid size in CSS grid

I am currently trying to complete an etch-a-sketch project but am having issues with setting a grid to respect a strict size value. I want to set it so that the grid squares become smaller and the overall container remains the same however what I currently have forces the grid to go off the page.
I have tried max-height and max-width settings but these don't seem to do anything. Could someone please help me ?
Javascript Code
const button16 = document.querySelector('.smol');
const button24 = document.querySelector('.med');
const button64 = document.querySelector('.large');
const button = document.querySelector('.reset');
const container = document.querySelector('.container');
function row(num, columns){
for (let i = 0; i < num; i++){
const div = document.createElement('div')
container.appendChild(div);
for (let i = 0; i < columns; i++){
const div2 = document.createElement('div2', );
div.appendChild(div2);
}
}
}
hover(row(16, 16))
button16.addEventListener('click', () => {
clearGrid();
hover(row(16, 16))
});
button24.addEventListener('click', () => {
clearGrid();
hover(row(24, 24))
});
button64.addEventListener('click', () => {
clearGrid();
hover(row(64, 64))
});
function hover() {
const wrapper2 = container.querySelectorAll('div2');
wrapper2.forEach(wrapper2 => wrapper2.addEventListener('mouseenter', () => {
const hash = '#';
let randomColor = Math.floor(Math.random()*16777215).toString(16);
wrapper2.style.backgroundColor = hash + randomColor;
}));
wrapper2.forEach(wrapper2 => button.addEventListener("click", () => {
wrapper2.style.backgroundColor = 'white';
}));
}
function clearGrid() {
container.innerHTML = null
}
My CSS
.container {
display: grid;
grid-template-columns:repeat(64, 1fr);
width: 500px;
height: 500px;
}
div2 {
border: solid 0.1px;
color: #424242;
display: grid;
width:30px;
height: 25px;
box-sizing: border-box
}
.buttonbox {
display: flex;
width: 500px;
}
.reset, .smol, .med, .large {
padding: 5px;
}
My html
<!DOCTYPE HTML>
<html lan="en">
<head>
<meta charset = utf-8>
<link rel= 'stylesheet' href="Styles.css" ></script>
<script src = GameLogic.js defer></script>
<title>My Etch-A-Sketch Project</title>
</head>
<body>
<h1>Etch-A-Sketch</h1>
<div class = "container">
</div>
<div class = 'buttonbox'>
<button class="reset">
Clear Grid
</button>
<button class="smol">
16 x 16
</button>
<button class="med">
24 x 24
</button>
<button class="large">
64 x 64
</button>
</div>
</body>
</html>

how to reset color grid?

I am creating a simple etch-a-sketch game. currently on hover it colors in black. I am trying to use a button to reset the colors back to white. However, i can't get the button to function with an event listener, if i add an alert it displays the alert but nothing else. Please guide me and supply a documentation that I can reference as I want to learn and fixing it without explaining will be counterproductive at this point.
Thank you !
const containerGrid = document.getElementById("mainGrid");
function makeGrid(col) {
for (let i = 0; i < col * col; i++) {
const gridAdd = document.createElement("div");
gridAdd.classList.add("box");
gridAdd.textContent = "";
containerGrid.appendChild(gridAdd);
}
}
makeGrid(16); // make grid 16*16
const btnClear = document.getElementById("clear");
//mouseover event black - need to link to button (well done :)
const boxes = document.querySelectorAll('.box').forEach(item => {
item.addEventListener('mouseover', event => {
item.style.backgroundColor = "black";
})
});
btnClear.addEventListener("click", () => {
boxes.style.backgroundColor = "white";
});
const changeGrid = document.getElementById(".sizechange");
/*clearBtn.forEach.addEventListener("click", function () {
clearBtn.style.color ="white";
});
*/
/*const randomBtn = document.getElementById("randomgen").addEventListener('click',(e) => {
console.log(this.classname)
console.log(e.currentTarget === this)
}) */
//change color
#mainGrid {
display: grid;
justify-content: center;
align-items: center;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: auto;
margin-left: 150px;
width: 200px;
}
.box {
color: black;
border: 3px solid;
height: 10px;
width: 10px;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Etch-a-Sketch</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
<div id="colorContainer">
<input type="radio" id="blackchoice" value="color" name="black" class="defaultbtn">
<label for="defaultcolor">black</label>
<input type="radio" id="randomgen" class="hey">
<label for="randomchoice">random</label>
</div>
<div id="changeGrid">
<button id="clear">clear</button>
</div>
<div id="mainGrid"></div>
<script src="app.js"></script>
</body>
</html>
A couple of related problems:
The variable boxes is undefined. It looks as though it was required to be the set elements with class box. When it is being defined this is indeed done, but then made undefined by the forEach attached to it. Separate out these two things and boxes will become the collection of all elements with class box.
Then when the clear is clicked you need to step through each of these boxes making their background color white, so again use a forEach.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Etch-a-Sketch</title>
<link type="text/css" rel="stylesheet" href="styles.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
#mainGrid {
display: grid;
justify-content: center;
align-items: center;
grid-template-columns: repeat(16, 1fr);
grid-template-rows: auto;
margin-left: 150px;
width: 200px;
}
.box {
color: black;
border: 3px solid;
height: 10px;
width: 10px;
}
</style>
</head>
<body>
<div id="colorContainer">
<input type="radio" id="blackchoice" value="color" name="black" class="defaultbtn">
<label for="defaultcolor">black</label>
<input type="radio" id="randomgen" class="hey">
<label for="randomchoice">random</label>
</div>
<div id="changeGrid">
<button id="clear">clear</button>
</div>
<div id="mainGrid"></div>
<script src="app.js"></script>
<script>
const containerGrid = document.getElementById("mainGrid");
function makeGrid(col) {
for (let i = 0; i < col * col; i++) {
const gridAdd = document.createElement("div");
gridAdd.classList.add("box");
gridAdd.textContent = "";
containerGrid.appendChild(gridAdd);
}
}
makeGrid(16); // make grid 16*16
const btnClear = document.getElementById("clear");
//mouseover event black - need to link to button (well done :)
const boxes = document.querySelectorAll('.box');
boxes.forEach(box => {
box.addEventListener('mouseover', event => {
box.style.backgroundColor = "black";
})
});
btnClear.addEventListener("click", () => {
boxes.forEach(box => {
box.style.backgroundColor = "white";
});
});
const changeGrid = document.getElementById(".sizechange");
/*clearBtn.forEach.addEventListener("click", function () {
clearBtn.style.color ="white";
});
*/
/*const randomBtn = document.getElementById("randomgen").addEventListener('click',(e) => {
console.log(this.classname)
console.log(e.currentTarget === this)
}) */
//change color
</script>
</body>
</html>
Simplify your CSS
Use a SELECT element for your colors
Define the gridTemplateColumns in JS, not in CSS.
Use simpler functions
Use global variables to store the current grid size and color
Don't forget to clear your grid before changing the size
Assign the mouseenter Event on each cell on creation!
Add a boolean variable isPenDown for a better user experience!
const NewEL = (sel, prop) => Object.assign(document.createElement(sel), prop);
const EL_grid = document.querySelector("#grid");
const EL_clear = document.querySelector("#clear");
const EL_color = document.querySelector("[name=color]");
const EL_size = document.querySelector("[name=size]");
let size = parseInt(EL_size.value, 10);
let color = "black";
let isPenDown = false;
function makeGrid() {
EL_grid.innerHTML = ""; // Clear current grid!
for (let i = 0; i < size ** 2; i++) {
EL_grid.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
EL_grid.append(NewEL("div", {
className: "box",
onmousedown() { isPenDown = true; paint(this); },
onmouseup() { isPenDown = false; },
onmouseenter() { if (isPenDown) paint(this); },
}));
}
};
function paint(EL) {
EL.style.backgroundColor = color;
}
EL_clear.addEventListener("click", () => {
const tmp_color = color; // Remember current color
color = "transparent"; // Temporarily set it to transparent
EL_grid.querySelectorAll(".box").forEach(paint); // Paint all cells as transparent
color = tmp_color; //* Reset as it was before.
});
EL_color.addEventListener("change", () => {
color = EL_color.value;
if (color === "random") color = `hsl(${~~(Math.random() * 360)}, 80%, 50%)`;
});
EL_size.addEventListener("change", () => {
size = parseInt(EL_size.value, 10);
makeGrid();
});
// INIT!
makeGrid();
#grid {
display: inline-grid;
margin: 10px 0;
}
#grid .box {
border: 1px solid;
height: 10px;
width: 10px;
margin: 0;
user-select: none;
}
<div>
<label>
Size:
<input type="number" name="size" value="16">
</label>
<label>
Color:
<select name="color">
<option value="black">black</option>
<option value="white">white</option>
<option value="red">red</option>
<option value="yellow">yellow</option>
<option value="orange">orange</option>
<option value="fuchsia">fuchsia</option>
<option value="transparent">CLEAR (transparent)</option>
<option value="random">RANDOM COLOR</option>
</select>
</label>
<button id="clear">Clear canvas</button>
</div>
<div id="grid"></div>

If condition not working with device orientation

So guys, I am playing with device orientation.
Here is it https://sublime.glitch.me/
Everything went well except the if condition.
If you could please look at the webpage on mobile device, you will see the png moves according to the device orientation.
But I dont want it to go off the screen, this is where I need if condition. I tried to make the evt.gamma not greater than 35 or smaller than -35 with if condition.
But apparently, what I have is not working. Sorry for my poor coding knowledge, I am a designer not a developer, can someone please help me not?
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
e=o.createElement(i);r=o.getElementsByTagName(i)[0];
e.src='//www.google-analytics.com/analytics.js';
r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
ga('create','UA-52746336-1');ga('send','pageview');
var isCompleted = {};
function sampleCompleted(sampleName){
if (ga && !isCompleted.hasOwnProperty(sampleName)) {
ga('send', 'event', 'WebCentralSample', sampleName, 'completed');
isCompleted[sampleName] = true;
}
}
var fixed = 2;
var h5logo = document.getElementById("h5logo");
var timestamp = document.getElementById("timestamp");
var alpha = document.getElementById("alpha");
var beta = document.getElementById("beta");
var gamma = document.getElementById("gamma");
/* // [START devori] */
if (window.DeviceOrientationEvent) {
window.addEventListener('deviceorientation', deviceOrientationHandler, false);
document.getElementById("doeSupported").innerText = "";
}
/* // [END devori] */
var deviceOrientationData;
function deviceOrientationHandler(evt) {
deviceOrientationData = evt;
try {
timestamp.innerText = new Date(evt.timeStamp);
alpha.innerText = evt.alpha.toFixed(fixed);
beta.innerText = evt.beta.toFixed(fixed);
gamma.innerText = evt.gamma.toFixed(fixed);
if(evt.gamma<-35){evt.gamma=-35}
if(evt.gamma>35){evt.gamma=35}
var trans = " translate("+ ((evt.gamma+20) * 2)+"px, "+ ((evt.beta-90) * -3)+"px) " ;
h5logo.style.webkitTransform = trans;
h5logo.style.transform = trans;
} catch (ex) {
document.getElementById("doeSupported").innerText = "NOT";
}
}
.h5logo {
width: 40vh;
display: block;
margin-left: auto;
margin-right: auto;
position:absolute;
z=index:10;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<script defer src="https://code.getmdl.io/1.2.1/material.min.js"> .
</script>
<style>
body {
margin: 2em;
}
</style>
<title>👌</title>
</head>
<body>
<div role="main" class="container">
<img src="https://cdn.glitch.com/8da575e7-c6aa-48cb-bb49-3a708f8c28fc%2Fpreview.png?1535142774062" style="display: block; position: absolute; margin-left:auto; margin-right:auto; z-index:-2">
<img id="h5logo" src="https://cdn.glitch.com/8da575e7-c6aa-48cb-bb49-3a708f8c28fc%2Fhand.png?1535129311138" class="h5logo">
<h1 style="position:absolute; z-index:-1; transform: rotate(90deg) translate(20%,-60%);">
sublime
</h1>
<p>
<b>alpha:</b> <span id="alpha"></span><br>
<b>beta:</b> <span id="beta"></span><br>
<b>gamma:</b> <span id="gamma"></span><br>
</p>
<p>
<span id="timestamp" ></span>
</p>
</div>
</body>
</html>
The lines not working are here, the translation works fine, just the if condition. Thanks so much guys.
if(evt.gamma<-35){evt.gamma=-35}
if(evt.gamma>35){evt.gamma=35}
var trans = " translate("+ ((evt.gamma+20) * 2)+"px, "+
((evt.beta-90) * -3)+"px) " ;
All event properties, including gamma are read-only, so you need to assign -35 and 35 to a new variable. See the documentation.
Example:
let newGamma = evt.gamma;
if(evt.gamma < -35){
newGamma = -35;
}
else if(evt.gamma > 35){
newGamma = 35;
}
var trans = " translate(" + ((newGamma + 20) * 2) + "px, " + ((evt.beta - 90) * -3) + "px) ";
Simpler example, using Math methods:
var trans = " translate(" + ((Math.min(Math.max(evt.gamma, -35), 35) + 20) * 2) + "px, " + ((evt.beta - 90) * -3) + "px) ";

Gecko - CSS layout border radius - Javascript conversion

I'm trying to get the exact formula in order to compute CSS border-radius property in a canvas. I've already tried and have an example in javascript (see below), but with no success.
Its seems that the browser is still adding some adaptations in order to ajust the borders. And i'm unable to identify them. So i've checked the sources of the Gecko layout engine, but i'm not sure where i can find this formula (in the sources).
It may be in layout/painting/nsCSSRenderingBorders.cpp, but there's still a lot of code and it's C++ (i've no skills into that language)
See Gecko repository : https://github.com/mozilla/gecko-dev
So, if anyone could help me in order to achieve this adaptation, or give me the blocks of code which are computing border-radius "arcs, orientation ?" (in gecko) i'll be able to do that.
Javascript/HTML snippet (current, close from good adaptation)
(RED SHAPE = Canvas shape, GREEN SHAPE = HTML shape)
I'm using this function to draw the shape : ctx.constructor.prototype.fillRoundedRect
And this function in order to get close from the browser adaptation : correctRadius
As you will see, im getting this result when TopLEFT, TopRight and BottomLEFT sliders are at max value. The browser (green) is rendering it perfectly, and mine is bad (red).
See snippet below
// Ctx
var ctx = document.getElementById("rounded-rect").getContext("2d");
function correctRadius(r, w, h) {
if (r.tl + r.tr > w) {
r.tl -= (r.tl + r.tr - w) / 2;
r.tr = w - r.tl;
}
if (r.bl + r.br > w) {
r.br -= (r.br + r.bl - w) / 2;
r.bl = w - r.br;
}
if (r.tl + r.bl > h) {
r.tl -= (r.tl + r.bl - h) / 2;
r.bl = h - r.tl;
}
if (r.tr + r.br > h) {
r.tr -= (r.tr + r.br - h) / 2;
r.br = h - r.tr;
}
}
//Round rect func
ctx.constructor.prototype.fillRoundedRect =
function (xx, yy, ww, hh, rad, fill, stroke) {
correctRadius(rad, ww, hh);
if (typeof(rad) === "undefined") rad = 5;
this.beginPath();
this.moveTo(xx, yy);
this.arcTo(xx + ww, yy, xx + ww, yy + hh, rad.tr);
this.arcTo(xx + ww, yy + hh, xx, yy + hh, rad.br);
this.arcTo(xx, yy + hh, xx, yy, rad.bl);
this.arcTo(xx, yy, xx + ww, yy, rad.tl);
if (stroke) this.stroke(); // Default to no stroke
if (fill || typeof(fill) === "undefined") this.fill(); // Default to fill
};
ctx.fillStyle = "red";
ctx.strokeStyle = "#ddf";
var copy = document.getElementById('copy');
var tl = document.getElementById('tl');
var tr = document.getElementById('tr');
var bl = document.getElementById('bl');
var br = document.getElementById('br');
var off = document.getElementById('off');
function test() {
ctx.clearRect(0, 0, 600, 500);
/* 1.Top left */
/* 2. Top right */
/* 3. Bottom right */
/* 4. Bottom left */
var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';
copy.style.borderRadius = borders;
var copyRad = borders.replace(/px/g, '').split(' ').map(function (a) {
return parseInt(a)
});
var rad = {
tl: copyRad[0],
tr: copyRad[1],
br: copyRad[2],
bl: copyRad[3]
};
var o = +off.value;
ctx.fillRoundedRect(15 + o, 15 + o, 100, 100, rad);
}
tl.oninput = test;
tr.oninput = test;
bl.oninput = test;
br.oninput = test;
off.oninput = test;
test();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html, body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 100px; background:green;
border-radius: 10px 5px 10px 20px;" id="copy">
</div>
<canvas style="display: inline-block; position: absolute; zindex:0; left:0; top:0;" id="rounded-rect" width="600" height="500">
</canvas>
<div style="top: 300px; position:absolute; z-index: 1;">
<label>
Top left
<input type="range" min="1" max="100" value="0" class="slider" id="tl"></label><br/>
<label>
Top right
<input type="range" min="1" max="100" value="0" class="slider" id="tr"></label><br/>
<label>
Bottom left
<input type="range" min="1" max="100" value="0" class="slider" id="bl"></label><br/>
<label>
Bottom right
<input type="range" min="1" max="100" value="0" class="slider" id="br"></label><br/>
<label>
Offset
<input type="range" min="1" max="200" value="0" class="slider" id="off"></label><br/>
</div>
</body>
</html>
The issue is with your correctRadius function. As you can see you are re-calculating value more than once which is incorrect.
Let's take an example with TopLeft, TopRight and BottomLeft set at max values (100px):
1) You will consider the first condition and you will update your values like this:
TopLeft = 50px | TopRight = 50px (these are correct values)
2) Now you will consider the third condition as you have TL+TB (100px + 50px) > h (100px) and you will update the values like this:
TopLeft = 25px | BottomLeft 75px (these are wrong values and the correct ones should be 50px for both)
So you don't have to calculate values separately, you should consider all of them and do only one calculation for each side depending on initial values.
The idea is to consider the max value of the difference between two adjacent side. Let's consider the same example above and make 3 different values like this:
TL = 100px | TR = 90px | BL = 100px | BR = 0px
TL is adjacent to TR and BL:
Considering TR we will have 190px > 100px and thus TL = 45px
Considering BL we will have 200px > 100px and this TL = 50px
So we should consider the max value which is 50px. We do the same for the other sides.
Here is the full code:
// Ctx
var ctx = document.getElementById("rounded-rect").getContext("2d");
function correctRadius(r, w, h) {
var tl=r.tl;
var tr=r.tr;
var br=r.br;
var bl=r.bl;
r.tl -= Math.max(Math.max((tl + tr - w)/2,0),
Math.max((tl + bl - h)/2,0));
r.tr -= Math.max(Math.max((tr + tl - w)/2,0),
Math.max((tr + br - h)/2,0));
r.br -= Math.max(Math.max((br + bl - w)/2,0),
Math.max((br + tr - h)/2,0));
r.bl -= Math.max(Math.max((bl + br - w)/2,0),
Math.max((bl + tl - h)/2,0));
}
//Round rect func
ctx.constructor.prototype.fillRoundedRect =
function (xx, yy, ww, hh, rad, fill, stroke) {
correctRadius(rad, ww, hh);
if (typeof(rad) === "undefined") rad = 5;
this.beginPath();
this.moveTo(xx, yy);
this.arcTo(xx + ww, yy, xx + ww, yy + hh, rad.tr);
this.arcTo(xx + ww, yy + hh, xx, yy + hh, rad.br);
this.arcTo(xx, yy + hh, xx, yy, rad.bl);
this.arcTo(xx, yy, xx + ww, yy, rad.tl);
if (stroke) this.stroke(); // Default to no stroke
if (fill || typeof(fill) === "undefined") this.fill(); // Default to fill
};
ctx.fillStyle = "red";
ctx.strokeStyle = "#ddf";
var copy = document.getElementById('copy');
var tl = document.getElementById('tl');
var tr = document.getElementById('tr');
var bl = document.getElementById('bl');
var br = document.getElementById('br');
var off = document.getElementById('off');
function test() {
ctx.clearRect(0, 0, 600, 500);
/* 1.Top left */
/* 2. Top right */
/* 3. Bottom right */
/* 4. Bottom left */
var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';
copy.style.borderRadius = borders;
var copyRad = borders.replace(/px/g, '').split(' ').map(function (a) {
return parseInt(a)
});
var rad = {
tl: copyRad[0],
tr: copyRad[1],
br: copyRad[2],
bl: copyRad[3]
};
var o = +off.value;
ctx.fillRoundedRect(15 + o, 15 + o, 100, 100, rad);
}
tl.oninput = test;
tr.oninput = test;
bl.oninput = test;
br.oninput = test;
off.oninput = test;
test();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
html, body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 100px; background:green;
border-radius: 10px 5px 10px 20px;" id="copy">
</div>
<canvas style="display: inline-block; position: absolute; zindex:0; left:0; top:0;" id="rounded-rect" width="600" height="500">
</canvas>
<div style="top: 300px; position:absolute; z-index: 1;">
<label>
Top left
<input type="range" min="1" max="100" value="0" class="slider" id="tl"></label><br/>
<label>
Top right
<input type="range" min="1" max="100" value="0" class="slider" id="tr"></label><br/>
<label>
Bottom left
<input type="range" min="1" max="100" value="0" class="slider" id="bl"></label><br/>
<label>
Bottom right
<input type="range" min="1" max="100" value="0" class="slider" id="br"></label><br/>
<label>
Offset
<input type="range" min="1" max="200" value="0" class="slider" id="off"></label><br/>
</div>
</body>
</html>

Categories