Skip to content

Instantly share code, notes, and snippets.

@simonemarra
Created November 21, 2018 11:37
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 simonemarra/0140c7083d819fc99d2be2b9e3da8b8f to your computer and use it in GitHub Desktop.
Save simonemarra/0140c7083d819fc99d2be2b9e3da8b8f to your computer and use it in GitHub Desktop.
SVG Path Builder
<div
id="app"
class="ad-App">
</div>
const Component = React.Component
const render = ReactDOM.render
class Container extends Component {
state = {
w: 800,
h: 600,
grid: {
show: true,
snap: true,
size: 50
},
ctrl: false,
points: [
{ x: 100, y: 300 },
{ x: 200, y: 300, q: { x: 150, y: 50 } },
{ x: 300, y: 300, q: { x: 250, y: 550 } },
{ x: 400, y: 300, q: { x: 350, y: 50 } },
{ x: 500, y: 300, c: [{ x: 450, y: 550 }, { x: 450, y: 50 }] },
{ x: 600, y: 300, c: [{ x: 550, y: 50 }, { x: 550, y: 550 }] },
{ x: 700, y: 300, a: { rx: 50, ry: 50, rot: 0, laf: 1, sf: 1 } }
],
activePoint: 2,
draggedPoint: false,
draggedQuadratic: false,
draggedCubic: false,
closePath: false
};
componentWillMount() {
document.addEventListener("keydown", this.handleKeyDown, false)
document.addEventListener("keyup", this.handleKeyUp, false)
}
componentWillUnmount() {
document.removeEventListener("keydown")
document.removeEventListener("keyup")
}
positiveNumber(n) {
n = parseInt(n)
if (isNaN(n) || n < 0) n = 0
return n
}
setWidth = (e) => {
let v = this.positiveNumber(e.target.value), min = 1
if (v < min) v = min
this.setState({ w: v })
};
setHeight = (e) => {
let v = this.positiveNumber(e.target.value), min = 1
if (v < min) v = min
this.setState({ h: v })
};
setGridSize = (e) => {
let grid = this.state.grid
let v = this.positiveNumber(e.target.value)
let min = 1
let max = Math.min(this.state.w, this.state.h)
if (v < min) v = min
if (v >= max) v = max / 2
grid.size = v
this.setState({ grid })
};
setGridSnap = (e) => {
let grid = this.state.grid
grid.snap = e.target.checked
this.setState({ grid })
};
setGridShow = (e) => {
let grid = this.state.grid
grid.show = e.target.checked
this.setState({ grid })
};
setClosePath = (e) => {
this.setState({ closePath: e.target.checked })
};
getMouseCoords = (e) => {
const rect = ReactDOM.findDOMNode(this.refs.svg).getBoundingClientRect()
let x = Math.round(e.pageX - rect.left)
let y = Math.round(e.pageY - rect.top)
if (this.state.grid.snap) {
x = this.state.grid.size * Math.round(x / this.state.grid.size)
y = this.state.grid.size * Math.round(y / this.state.grid.size)
}
return { x, y }
};
setPointType = (e) => {
const points = this.state.points
const active = this.state.activePoint
// not the first point
if (active !== 0) {
let v = e.target.value
switch (v) {
case "l":
points[active] = {
x: points[active].x,
y: points[active].y
}
break
case "q":
points[active] = {
x: points[active].x,
y: points[active].y,
q: {
x: (points[active].x + points[active - 1].x) / 2,
y: (points[active].y + points[active - 1].y) / 2
}
}
break
case "c":
points[active] = {
x: points[active].x,
y: points[active].y,
c: [
{
x: (points[active].x + points[active - 1].x - 50) / 2,
y: (points[active].y + points[active - 1].y) / 2
},
{
x: (points[active].x + points[active - 1].x + 50) / 2,
y: (points[active].y + points[active - 1].y) / 2
}
]
}
break
case "a":
points[active] = {
x: points[active].x,
y: points[active].y,
a: {
rx: 50,
ry: 50,
rot: 0,
laf: 1,
sf: 1
}
}
break
}
this.setState({ points })
}
};
setPointPosition = (coord, e) => {
let coords = this.state.points[this.state.activePoint]
let v = this.positiveNumber(e.target.value)
if (coord === "x" && v > this.state.w) v = this.state.w
if (coord === "y" && v > this.state.h) v = this.state.h
coords[coord] = v
this.setPointCoords(coords)
};
setQuadraticPosition = (coord, e) => {
let coords = this.state.points[this.state.activePoint].q
let v = this.positiveNumber(e.target.value)
if (coord === "x" && v > this.state.w) v = this.state.w
if (coord === "y" && v > this.state.h) v = this.state.h
coords[coord] = v
this.setQuadraticCoords(coords)
};
setCubicPosition = (coord, anchor, e) => {
let coords = this.state.points[this.state.activePoint].c[anchor]
let v = this.positiveNumber(e.target.value)
if (coord === "x" && v > this.state.w) v = this.state.w
if (coord === "y" && v > this.state.h) v = this.state.h
coords[coord] = v
this.setCubicCoords(coords, anchor)
};
setPointCoords = (coords) => {
const points = this.state.points
const active = this.state.activePoint
points[active].x = coords.x
points[active].y = coords.y
this.setState({ points })
};
setQuadraticCoords = (coords) => {
const points = this.state.points
const active = this.state.activePoint
points[active].q.x = coords.x
points[active].q.y = coords.y
this.setState({ points })
};
setArcParam = (param, e) => {
const points = this.state.points
const active = this.state.activePoint
let v
if (["laf", "sf"].indexOf(param) > -1) {
v = e.target.checked ? 1 : 0
} else {
v = this.positiveNumber(e.target.value)
}
points[active].a[param] = v
this.setState({ points })
};
setCubicCoords = (coords, anchor) => {
const points = this.state.points
const active = this.state.activePoint
points[active].c[anchor].x = coords.x
points[active].c[anchor].y = coords.y
this.setState({ points })
};
setDraggedPoint = (index) => {
if ( ! this.state.ctrl) {
this.setState({
activePoint: index,
draggedPoint: true
})
}
};
setDraggedQuadratic = (index) => {
if ( ! this.state.ctrl) {
this.setState({
activePoint: index,
draggedQuadratic: true
})
}
};
setDraggedCubic = (index, anchor) => {
if ( ! this.state.ctrl) {
this.setState({
activePoint: index,
draggedCubic: anchor
})
}
};
cancelDragging = (e) => {
this.setState({
draggedPoint: false,
draggedQuadratic: false,
draggedCubic: false
})
};
addPoint = (e) => {
if (this.state.ctrl) {
let coords = this.getMouseCoords(e)
let points = this.state.points
points.push(coords)
this.setState({
points,
activePoint: points.length - 1
})
}
};
removeActivePoint = (e) => {
let points = this.state.points
let active = this.state.activePoint
if (points.length > 1 && active !== 0) {
points.splice(active, 1)
this.setState({
points,
activePoint: points.length - 1
})
}
};
handleMouseMove = (e) => {
if ( ! this.state.ctrl) {
if (this.state.draggedPoint) {
this.setPointCoords(this.getMouseCoords(e))
} else if (this.state.draggedQuadratic) {
this.setQuadraticCoords(this.getMouseCoords(e))
} else if (this.state.draggedCubic !== false) {
this.setCubicCoords(this.getMouseCoords(e), this.state.draggedCubic)
}
}
};
handleKeyDown = (e) => {
if (e.ctrlKey) this.setState({ ctrl: true })
};
handleKeyUp = (e) => {
if ( ! e.ctrlKey) this.setState({ ctrl: false })
};
generatePath() {
let { points, closePath } = this.state
let d = ""
points.forEach((p, i) => {
if (i === 0) {
// first point
d += "M "
} else if (p.q) {
// quadratic
d += `Q ${ p.q.x } ${ p.q.y } `
} else if (p.c) {
// cubic
d += `C ${ p.c[0].x } ${ p.c[0].y } ${ p.c[1].x } ${ p.c[1].y } `
} else if (p.a) {
// arc
d += `A ${ p.a.rx } ${ p.a.ry } ${ p.a.rot } ${ p.a.laf } ${ p.a.sf } `
} else {
d += "L "
}
d += `${ p.x } ${ p.y } `
})
if (closePath) d += "Z"
return d
}
reset = (e) => {
let w = this.state.w, h = this.state.h
this.setState({
points: [{ x: w / 2, y: h / 2 }],
activePoint: 0
})
};
render() {
return (
<div
className="ad-Container"
onMouseUp={ this.cancelDragging }>
<div className="ad-Container-main">
<div className="ad-Container-svg">
<SVG
ref="svg"
path={ this.generatePath() }
{ ...this.state }
addPoint={ this.addPoint }
setDraggedPoint={ this.setDraggedPoint }
setDraggedQuadratic={ this.setDraggedQuadratic }
setDraggedCubic={ this.setDraggedCubic }
handleMouseMove={ this.handleMouseMove } />
</div>
<Foot />
</div>
<div className="ad-Container-controls">
<Controls
{ ...this.state }
reset={ this.reset }
removeActivePoint={ this.removeActivePoint }
setPointPosition={ this.setPointPosition }
setQuadraticPosition={ this.setQuadraticPosition }
setCubicPosition={ this.setCubicPosition }
setArcParam={ this.setArcParam }
setPointType={ this.setPointType }
setWidth={ this.setWidth }
setHeight={ this.setHeight }
setGridSize={ this.setGridSize }
setGridSnap={ this.setGridSnap }
setGridShow={ this.setGridShow }
setClosePath={ this.setClosePath } />
<Result path={ this.generatePath() } />
</div>
</div>
)
}
}
function Foot(props) {
return (
<div className="ad-Foot">
<ul className="ad-Foot-list">
<li className="ad-Foot-item">
<span className="ad-Foot-highlight">Click</span> to select a point
</li>
<li className="ad-Foot-item">
<span className="ad-Foot-highlight">Ctrl + Click</span> to add a point
</li>
</ul>
<div className="ad-Foot-meta">
<a href="https://twitter.com/a_dugois">Follow me on Twitter</a><br />
or <a href="http://anthonydugois.com/svg-path-builder/">check the online version</a>
</div>
</div>
)
}
function Result(props) {
return (
<div className="ad-Result">
<textarea
className="ad-Result-textarea"
value={ props.path }
onFocus={ (e) => e.target.select() } />
</div>
)
}
/**
* SVG rendering
*/
class SVG extends Component {
render() {
const {
path,
w,
h,
grid,
points,
activePoint,
addPoint,
handleMouseMove,
setDraggedPoint,
setDraggedQuadratic,
setDraggedCubic
} = this.props
let circles = points.map((p, i, a) => {
let anchors = []
if (p.q) {
anchors.push(
<Quadratic
index={ i }
p1x={ a[i - 1].x }
p1y={ a[i - 1].y }
p2x={ p.x }
p2y={ p.y }
x={ p.q.x }
y={ p.q.y }
setDraggedQuadratic={ setDraggedQuadratic } />
)
} else if (p.c) {
anchors.push(
<Cubic
index={ i }
p1x={ a[i - 1].x }
p1y={ a[i - 1].y }
p2x={ p.x }
p2y={ p.y }
x1={ p.c[0].x }
y1={ p.c[0].y }
x2={ p.c[1].x }
y2={ p.c[1].y }
setDraggedCubic={ setDraggedCubic } />
)
}
return (
<g className={
"ad-PointGroup" +
(i === 0 ? " ad-PointGroup--first" : "") +
(activePoint === i ? " is-active" : "")
}>
<Point
index={ i }
x={ p.x }
y={ p.y }
setDraggedPoint={ setDraggedPoint } />
{ anchors }
</g>
)
})
return (
<svg
className="ad-SVG"
width={ w }
height={ h }
onClick={ (e) => addPoint(e) }
onMouseMove={ (e) => handleMouseMove(e) }>
<Grid
w={ w }
h={ h }
grid={ grid } />
<path
className="ad-Path"
d={ path } />
<g className="ad-Points">
{ circles }
</g>
</svg>
)
}
}
function Cubic(props) {
return (
<g className="ad-Anchor">
<line
className="ad-Anchor-line"
x1={ props.p1x }
y1={ props.p1y }
x2={ props.x1 }
y2={ props.y1 } />
<line
className="ad-Anchor-line"
x1={ props.p2x }
y1={ props.p2y }
x2={ props.x2 }
y2={ props.y2 } />
<circle
className="ad-Anchor-point"
onMouseDown={ (e) => props.setDraggedCubic(props.index, 0) }
cx={ props.x1 }
cy={ props.y1 }
r={ 6 } />
<circle
className="ad-Anchor-point"
onMouseDown={ (e) => props.setDraggedCubic(props.index, 1) }
cx={ props.x2 }
cy={ props.y2 }
r={ 6 } />
</g>
)
}
function Quadratic(props) {
return (
<g className="ad-Anchor">
<line
className="ad-Anchor-line"
x1={ props.p1x }
y1={ props.p1y }
x2={ props.x }
y2={ props.y } />
<line
className="ad-Anchor-line"
x1={ props.x }
y1={ props.y }
x2={ props.p2x }
y2={ props.p2y } />
<circle
className="ad-Anchor-point"
onMouseDown={ (e) => props.setDraggedQuadratic(props.index) }
cx={ props.x }
cy={ props.y }
r={ 6 } />
</g>
)
}
function Point(props) {
return (
<circle
className="ad-Point"
onMouseDown={ (e) => props.setDraggedPoint(props.index) }
cx={ props.x }
cy={ props.y }
r={ 8 } />
)
}
function Grid(props) {
const { show, snap, size } = props.grid
let grid = []
for (let i = 1 ; i < (props.w / size) ; i++) {
grid.push(
<line
x1={ i * size }
y1={ 0 }
x2={ i * size }
y2={ props.h } />
)
}
for (let i = 1 ; i < (props.h / size) ; i++) {
grid.push(
<line
x1={ 0 }
y1={ i * size }
x2={ props.w }
y2={ i * size } />
)
}
return (
<g className={
"ad-Grid" +
( ! show ? " is-hidden" : "")
}>
{ grid }
</g>
)
}
/**
* Controls
*/
function Controls(props) {
const active = props.points[props.activePoint]
const step = props.grid.snap ? props.grid.size : 1
let params = []
if (active.q) {
params.push(
<div className="ad-Controls-container">
<Control
name="Control point X position"
type="range"
min={ 0 }
max={ props.w }
step={ step }
value={ active.q.x }
onChange={ (e) => props.setQuadraticPosition("x", e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Control point Y position"
type="range"
min={ 0 }
max={ props.h }
step={ step }
value={ active.q.y }
onChange={ (e) => props.setQuadraticPosition("y", e) } />
</div>
)
} else if (active.c) {
params.push(
<div className="ad-Controls-container">
<Control
name="First control point X position"
type="range"
min={ 0 }
max={ props.w }
step={ step }
value={ active.c[0].x }
onChange={ (e) => props.setCubicPosition("x", 0, e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="First control point Y position"
type="range"
min={ 0 }
max={ props.h }
step={ step }
value={ active.c[0].y }
onChange={ (e) => props.setCubicPosition("y", 0, e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Second control point X position"
type="range"
min={ 0 }
max={ props.w }
step={ step }
value={ active.c[1].x }
onChange={ (e) => props.setCubicPosition("x", 1, e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Second control point Y position"
type="range"
min={ 0 }
max={ props.h }
step={ step }
value={ active.c[1].y }
onChange={ (e) => props.setCubicPosition("y", 1, e) } />
</div>
)
} else if (active.a) {
params.push(
<div className="ad-Controls-container">
<Control
name="X Radius"
type="range"
min={ 0 }
max={ props.w }
step={ step }
value={ active.a.rx }
onChange={ (e) => props.setArcParam("rx", e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Y Radius"
type="range"
min={ 0 }
max={ props.h }
step={ step }
value={ active.a.ry }
onChange={ (e) => props.setArcParam("ry", e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Rotation"
type="range"
min={ 0 }
max={ 360 }
step={ 1 }
value={ active.a.rot }
onChange={ (e) => props.setArcParam("rot", e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Large arc sweep flag"
type="checkbox"
checked={ active.a.laf }
onChange={ (e) => props.setArcParam("laf", e) } />
</div>
)
params.push(
<div className="ad-Controls-container">
<Control
name="Sweep flag"
type="checkbox"
checked={ active.a.sf }
onChange={ (e) => props.setArcParam("sf", e) } />
</div>
)
}
return (
<div className="ad-Controls">
<h3 className="ad-Controls-title">
Parameters
</h3>
<div className="ad-Controls-container">
<Control
name="Width"
type="text"
value={ props.w }
onChange={ (e) => props.setWidth(e) } />
<Control
name="Height"
type="text"
value={ props.h }
onChange={ (e) => props.setHeight(e) } />
<Control
name="Close path"
type="checkbox"
value={ props.closePath }
onChange={ (e) => props.setClosePath(e) } />
</div>
<div className="ad-Controls-container">
<Control
name="Grid size"
type="text"
value={ props.grid.size }
onChange={ (e) => props.setGridSize(e) } />
<Control
name="Snap grid"
type="checkbox"
checked={ props.grid.snap }
onChange={ (e) => props.setGridSnap(e) } />
<Control
name="Show grid"
type="checkbox"
checked={ props.grid.show }
onChange={ (e) => props.setGridShow(e) } />
</div>
<div className="ad-Controls-container">
<Control
type="button"
action="reset"
value="Reset path"
onClick={ (e) => props.reset(e) } />
</div>
<h3 className="ad-Controls-title">
Selected point
</h3>
{ props.activePoint !== 0 && (
<div className="ad-Controls-container">
<Control
name="Point type"
type="choices"
id="pointType"
choices={[
{ name: "L", value: "l", checked: (!active.q && !active.c && !active.a) },
{ name: "Q", value: "q", checked: !!active.q },
{ name: "C", value: "c", checked: !!active.c },
{ name: "A", value: "a", checked: !!active.a }
]}
onChange={ (e) => props.setPointType(e) } />
</div>
)}
<div className="ad-Controls-container">
<Control
name="Point X position"
type="range"
min={ 0 }
max={ props.w }
step={ step }
value={ active.x }
onChange={ (e) => props.setPointPosition("x", e) } />
</div>
<div className="ad-Controls-container">
<Control
name="Point Y position"
type="range"
min={ 0 }
max={ props.h }
step={ step }
value={ active.y }
onChange={ (e) => props.setPointPosition("y", e) } />
</div>
{ params }
{ props.activePoint !== 0 && (
<div className="ad-Controls-container">
<Control
type="button"
action="delete"
value="Remove this point"
onClick={ (e) => props.removeActivePoint(e) } />
</div>
)}
</div>
)
}
function Control(props) {
const {
name,
type,
..._props
} = props
let control = "", label = ""
switch (type) {
case "range": control = <Range { ..._props } />
break
case "text": control = <Text { ..._props } />
break
case "checkbox": control = <Checkbox { ..._props } />
break
case "button": control = <Button { ..._props } />
break
case "choices": control = <Choices { ..._props } />
break
}
if (name) {
label = (
<label className="ad-Control-label">
{ name }
</label>
)
}
return (
<div className="ad-Control">
{ label }
{ control }
</div>
)
}
function Choices(props) {
let choices = props.choices.map((c, i) => {
return (
<label className="ad-Choice">
<input
className="ad-Choice-input"
type="radio"
value={ c.value }
checked={ c.checked }
name={ props.id }
onChange={ props.onChange } />
<div className="ad-Choice-fake">
{ c.name }
</div>
</label>
)
})
return (
<div className="ad-Choices">
{ choices }
</div>
)
}
function Button(props) {
return (
<button
className={
"ad-Button" +
(props.action ? " ad-Button--" + props.action : "")
}
type="button"
onClick={ props.onClick }>
{ props.value }
</button>
)
}
function Checkbox(props) {
return (
<label className="ad-Checkbox">
<input
className="ad-Checkbox-input"
type="checkbox"
onChange={ props.onChange }
checked={ props.checked } />
<div className="ad-Checkbox-fake" />
</label>
)
}
function Text(props) {
return (
<input
className="ad-Text"
type="text"
value={ props.value }
onChange={ props.onChange } />
)
}
function Range(props) {
return (
<div className="ad-Range">
<input
className="ad-Range-input"
type="range"
min={ props.min }
max={ props.max }
step={ props.step }
value={ props.value }
onChange={ props.onChange } />
<input
className="ad-Range-text ad-Text"
type="text"
value={ props.value }
onChange={ props.onChange } />
</div>
)
}
render(
<Container />,
document.querySelector("#app")
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.0/react-dom.min.js"></script>
@use postcss-cssnext;
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600);
:root {
--ad-Color-prim: #111;
--ad-Color-sec: #00E676;
--ad-Color-del: #E53935;
}
html {
font-size: 16px;
font-family: "Open Sans", sans-serif;
}
html,
body {
height: 100%;
}
.ad-App {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
/* main layout */
.ad-Container {
height: 100%;
width: 100%;
display: flex;
background: #fff;
}
.ad-Container-main {
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
}
.ad-Container-svg {
height: 100%;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
background: #f3f3f3;
}
.ad-Container-controls {
overflow: hidden;
display: flex;
flex-direction: column;
height: 100%;
width: 20rem;
background: var(--ad-Color-prim);
}
.ad-Container-controls ::-webkit-scrollbar {
width: 6px;
}
.ad-Container-controls ::-webkit-scrollbar-thumb {
border-radius: 30px;
background: rgba(255, 255, 255, .3);
}
.ad-Foot {
padding: 1.5rem 2rem;
display: flex;
background: #fff;
border-top: 2px solid #eee;
}
.ad-Foot-list {
flex: 1;
}
.ad-Foot-item {
text-transform: uppercase;
font-size: .7rem;
color: var(--ad-Color-prim);
}
.ad-Foot-item + .ad-Foot-item {
margin-top: .5rem;
}
.ad-Foot-highlight {
padding-bottom: .04rem;
border-bottom: 2px solid var(--ad-Color-sec);
font-weight: bold;
}
.ad-Foot-meta {
margin-left: 2rem;
text-align: right;
line-height: 1.4;
font-size: .7rem;
color: var(--ad-Color-prim);
}
.ad-Foot-meta a {
text-decoration: underline;
color: var(--ad-Color-prim);
}
.ad-SVG {
display: block;
background: #fff;
border-radius: 4px;
}
.ad-Grid {
fill: none;
stroke: #eee;
stroke-width: 1px;
}
.ad-Grid.is-hidden {
opacity: 0;
}
.ad-Path {
fill: none;
stroke: #555;
stroke-width: 4px;
stroke-linecap: round;
}
.ad-Point {
cursor: pointer;
fill: #fff;
stroke: #555;
stroke-width: 5px;
transition: fill .2s;
}
.ad-Point:hover,
.ad-PointGroup.is-active .ad-Point {
fill: var(--ad-Color-sec);
}
.ad-PointGroup--first .ad-Point {
stroke: var(--ad-Color-sec);
}
.ad-Anchor {
opacity: .5;
}
.ad-PointGroup.is-active .ad-Anchor {
opacity: 1;
}
.ad-Anchor-point {
cursor: pointer;
fill: #fff;
stroke: #888;
stroke-width: 5px;
}
.ad-Anchor-line {
stroke: #888;
stroke-width: 1px;
stroke-dasharray: 5 5;
}
/* controls on the right */
.ad-Controls {
overflow: auto;
flex: 1;
padding: 2rem;
}
.ad-Controls :first-child {
margin-top: 0;
}
.ad-Controls-title {
margin: 3rem 0 1.5rem;
font-size: .8rem;
font-weight: bold;
color: #fff;
}
.ad-Controls-container {
display: flex;
}
.ad-Controls-container + .ad-Controls-container {
margin-top: 1.5rem;
}
.ad-Control {
flex: 1;
}
.ad-Control-label {
display: block;
margin-bottom: .5rem;
text-transform: uppercase;
font-size: .6rem;
font-weight: bold;
color: color(var(--ad-Color-prim) l(+75%));
}
.ad-Result {
height: 5rem;
padding: 1.4rem 1.6rem;
background: var(--ad-Color-prim);
box-shadow: 0 -5px 10px rgba(0, 0, 0, .4);
}
.ad-Result-textarea {
height: 100%;
width: 100%;
resize: none;
border: none;
background: none;
text-transform: uppercase;
font-family: "Open Sans", sans-serif;
font-size: .7rem;
font-weight: bold;
line-height: 1.8;
color: #fff;
}
.ad-Result-textarea:focus {
outline: 0;
}
/* control elements */
.ad-Button {
padding: .8rem 1.4rem;
background: var(--ad-Color-sec);
border: none;
border-radius: 50px;
cursor: pointer;
transition: background .2s;
text-transform: uppercase;
font-family: "Open Sans", sans-serif;
font-weight: bold;
font-size: .6rem;
letter-spacing: .08rem;
color: #fff;
}
.ad-Button:focus,
.ad-Button:hover {
outline: 0;
background: color(var(--ad-Color-sec) l(+5%));
}
.ad-Button--delete {
background: var(--ad-Color-del);
}
.ad-Button--delete:focus,
.ad-Button--delete:hover {
background: color(var(--ad-Color-del) l(+5%));
}
.ad-Button--reset {
background: color(var(--ad-Color-prim) l(+10%));
}
.ad-Button--reset:focus,
.ad-Button--reset:hover {
background: color(var(--ad-Color-prim) l(+15%));
}
.ad-Text {
height: 18px;
width: 2rem;
background: color(var(--ad-Color-prim) l(+10%));
border: none;
border-radius: 4px;
text-align: center;
font-family: "Open Sans", sans-serif;
font-size: .6rem;
color: #fff;
}
.ad-Text:focus {
outline: 0;
background: color(var(--ad-Color-prim) l(+20%));
}
.ad-Checkbox-input {
display: none;
}
.ad-Checkbox-fake {
position: relative;
height: 14px;
width: 2rem;
background: color(var(--ad-Color-prim) l(+10%));
border-radius: 30px;
}
.ad-Checkbox-fake::after {
content: "";
box-sizing: border-box;
display: block;
position: absolute;
top: -2px;
left: 0;
height: 18px;
width: 18px;
cursor: pointer;
border: 4px solid #fff;
background: color(var(--ad-Color-prim) l(+10%));
border-radius: 50%;
}
.ad-Checkbox-input:checked + .ad-Checkbox-fake::after {
left: auto;
right: 0;
border-color: var(--ad-Color-sec);
}
.ad-Choices {
display: flex;
width: 12rem;
}
.ad-Choice {
flex: 1;
}
.ad-Choice-input {
display: none;
}
.ad-Choice-fake {
padding: .6rem;
background: color(var(--ad-Color-prim) l(+10%));
border: 4px solid transparent;
transition: border .2s;
cursor: pointer;
text-align: center;
text-transform: uppercase;
font-family: "Open Sans", sans-serif;
font-size: .8rem;
font-weight: bold;
color: #fff;
}
.ad-Choice:first-child .ad-Choice-fake {
border-radius: 4px 0 0 4px;
}
.ad-Choice:last-child .ad-Choice-fake {
border-radius: 0 4px 4px 0;
}
.ad-Choice-input:checked + .ad-Choice-fake {
border-color: var(--ad-Color-sec);
}
.ad-Range {
display: flex;
align-items: center;
}
.ad-Range-text {
margin-left: .5rem;
}
.ad-Range-input {
width: 100%;
height: 14px;
appearance: none;
border-radius: 30px;
background: color(var(--ad-Color-prim) l(+10%));
}
.ad-Range-input:focus {
outline: 0;
background: color(var(--ad-Color-prim) l(+20%));
}
/* thumb */
.ad-Range-input::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border: 4px solid #fff;
background: color(var(--ad-Color-prim) l(+10%));
border-radius: 50%;
cursor: pointer;
transition: border .2s;
}
.ad-Range-input::-moz-range-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border: 4px solid #fff;
background: color(var(--ad-Color-prim) l(+10%));
border-radius: 50%;
cursor: pointer;
transition: border .2s;
}
.ad-Range-input:hover::-webkit-slider-thumb,
.ad-Range-input:focus::-webkit-slider-thumb {
border-color: var(--ad-Color-sec);
}
.ad-Range-input:hover::-moz-range-thumb,
.ad-Range-input:focus::-moz-range-thumb {
border-color: var(--ad-Color-sec);
}

SVG Path Builder

Build SVG paths easily using this GUI.

The main goal was to provide a quick way to get a path, without having to open tools like Adobe Illustrator. Made in 1000 lines using React v0.14.

A Pen by Simone Marra on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment