Skip to content

Instantly share code, notes, and snippets.

@Sphinxxxx
Last active April 10, 2018 20:08
Show Gist options
  • Save Sphinxxxx/e426ebe4753667697c3d5bfbc900a205 to your computer and use it in GitHub Desktop.
Save Sphinxxxx/e426ebe4753667697c3d5bfbc900a205 to your computer and use it in GitHub Desktop.
CSS gradient generator
<script>
//window.onerror = function(msg, url, line) { alert('Error: '+msg+'\nURL: '+url+'\nLine: '+line); };
</script>
<section id="app">
<h2>CSS gradient generator</h2>
<div id="settings">
<div class="tool-group">
<h3>Shape</h3>
<label>
<input type="radio" name="sh" v-model="g.shape" value="line"/> linear
</label>
<label>
<input type="radio" name="sh" v-model="g.shape" value="circle"/> circle
</label>
<label>
<input type="radio" name="sh" v-model="g.shape" value="ellipse"/> ellipse
</label>
</div>
<div class="tool-group">
<h3>Extent (or drag arrow)</h3>
<label>
<input type="radio" name="ext" v-model="g.extent" value="closest-corner"/> closest-corner
</label>
<label>
<input type="radio" name="ext" v-model="g.extent" value="closest-side"/> closest-side
</label>
<label>
<input type="radio" name="ext" v-model="g.extent" value="farthest-corner"/> farthest-corner
</label>
<label>
<input type="radio" name="ext" v-model="g.extent" value="farthest-side"/> farthest-side
</label>
<label>
<input type="checkbox" v-model="g.repeating" /> Repeating
</label>
</div>
<!--div class="tool-group">
<label>
<h3>Repeating</h3>
<input type="checkbox" v-model="g.repeating" />
</label>
</div-->
</div>
<div id="grad-container" :style="containerStyle">
<div id="gradient" :style="gradStyle">
</div>
<div id="controls">
<connector class="arrow" :start="g.start" :end="extentPoint"></connector>
<drag-point class="start" @drag_relay="setStart" :p="g.start" is-pct="true"></drag-point>
<drag-point class="ext" @drag_relay="setExtent" :p="extentPoint" is-pct="true"></drag-point>
<connector v-for="b in extentTangents" :start="b.start" :end="b.end"></connector>
<drag-point class="size" @drag_relay="setSize" :p="state.size"></drag-point> <drag-point class="size" @drag_relay="setSize" :p="state.size"></drag-point>
</div>
</div>
<div id="color-stops">
<h3>Color stops</h3>
<ul>
<li>Click the slider background to add a color</li>
<li>Double-click a marker to remove it</li>
</ul>
<div id="stops-slider">
<div id="stops-preview" :style="sliderStyle"></div>
<color-stop v-for="cs in g.stops" :cs="cs"></color-stop>
</div>
<div id="curr-stop" :style="{ opacity: g.currStop ? 1 : .2 }">
<div id="color-picker1"></div>
<div id="color-mode" v-if="g.currStop">
<label id="color-smooth"><span>Smooth</span><input type="radio" v-model="g.currStop.smooth" v-bind:value="true"/></label>
<label id="color-break" ><span>Break</span><input type="radio" v-model="g.currStop.smooth" v-bind:value="false"/></label>
</div>
<div id="color-picker2" :style="{ opacity: (g.currStop && !g.currStop.smooth) ? 1 : 0 }"></div>
</div>
</div>
<textarea id="out-css">{{ cssBackground }}</textarea>
</section>
"use strict";
console.clear();
//`${x} ${x} ${x} ${x}`
(function gradGen() {
/* Utils */
function getLength(start, end) {
const dx = end ? (end[0] - start[0]) : start[0],
dy = end ? (end[1] - start[1]) : start[1];
return Math.sqrt(dx*dx + dy*dy);
}
function getVector(start, end) {
const dx = end ? (end[0] - start[0]) : start[0],
dy = end ? (end[1] - start[1]) : start[1];
let length = Math.sqrt(dx*dx + dy*dy),
//https://gamedev.stackexchange.com/questions/33709/get-angle-in-radians-given-a-point-on-a-circle
radians = Math.atan2(dy, dx);
if(radians < 0) {
radians += Math.PI * 2;
}
return { length, radians };
}
// p1 & p2: Two points which the line passes through
// p0: A point outside the line, for which we'll find the closest point on the line
//https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line
function pointOnLine(p1, p2, p0) {
const x1 = p1[0], y1 = p1[1],
x2 = p2[0], y2 = p2[1],
x0 = p0[0], y0 = p0[1],
a = y2 - y1,
b = x1 - x2,
c = x2*y1 - y2*x1;
const x = (b*(b*x0 - a*y0) - a*c) /
(a*a + b*b),
y = (a*(-b*x0 + a*y0) - b*c) /
(a*a + b*b);
return [x, y];
}
/* /Utils */
//Global state model. Can be changed from within Vue or from the outside.
const __w = Math.min(window.innerWidth * .9, 500),
__h = __w * .7;
const _state =
{
size: [Math.round(__w), Math.round(__h)],
gradient: {
shape: 'ellipse',
start: [.19, .19],
extent: 'closest-corner',
//extent: [.23, .20],
stops: [
{
color1: 'rgba(255,241,97, 0)',
dist: .08,
smooth: false,
color2: 'rgb(255,253,20)',
//}, {
// color1: 'rgb(255,241,97)',
// dist: .16,
// smooth: false,
// color2: 'rgb(206,214,49)',
}, {
color1: 'rgb(206,214,49)',
dist: .37,
smooth: false,
color2: 'rgb(131,194,43)',
//}, {
// color1: 'rgb(131,194,43)',
// dist: .69,
// smooth: false,
// color2: 'rgba(80,141,133, 0.67)',
//}, {
// color1: 'rgba(80,141,133, 0.67)',
// dist: .75,
// smooth: true,
// color2: null,
}, {
color1: 'rgba(80,141,133, 0)',
dist: 1,
smooth: true,
color2: null,
},
],
currStop: null,
repeating: true,
},
};
function isLinear() {
return (_state.gradient.shape === 'line');
}
function setSize(size) {
const w = Math.max(0, size[0]),
h = Math.max(0, size[1]);
_state.size = [w, h];
}
function setStart(pixelPos) {
const size = _state.size;
_state.gradient.start = [pixelPos[0]/size[0], pixelPos[1]/size[1]];
};
function setExtent(pixelPos) {
const s = _state,
size = s.size,
g = s.gradient,
start = g.start;
const relPos = [pixelPos[0]/size[0], pixelPos[1]/size[1]];
g.extent = [relPos[0] - start[0], relPos[1] - start[1]];
}
function getExtentPos() {
const s = _state,
g = s.gradient,
start = g.start,
ext = g.extent;
let extPos;
if(Array.isArray(ext)) {
extPos = [ext[0] + start[0], ext[1] + start[1]];
}
else {
extPos = parseExtent();
}
return extPos;
}
function getExtentVector() {
const s = _state,
w = s.size[0],
h = s.size[1],
g = s.gradient,
start = g.start,
ext = getExtentPos();
const pxStart = [start[0] * w, start[1] * h],
pxExt = [ext[0] * w, ext[1] * h],
vector = getVector(pxStart, pxExt);
return vector;
}
function getRelExtentCorner() {
const s = _state,
g = s.gradient,
ext = g.extent;
let corner;
if(Array.isArray(ext)) {
if(g.shape === 'circle') {
//Circle: Largest bounding square
const w = s.size[0],
h = s.size[1],
extW = Math.abs(ext[0] * w),
extH = Math.abs(ext[1] * h);
corner = (extW > extH)
? [ext[0], extW/h * (ext[1]<0 ? -1 : 1)]
: [extH/w * (ext[0]<0 ? -1 : 1), ext[1]];
}
else {
corner = ext.slice();
}
}
return corner;
}
function getExtentTangents() {
const s = _state,
g = s.gradient,
tangents = [];
function extend(tang, factor) {
const dx = tang.end[0] - tang.start[0],
dy = tang.end[1] - tang.start[1];
tang.end = [
tang.end[0] + dx * factor,
tang.end[1] + dy * factor,
];
return tang;
}
if(isLinear()) {
const extVec = getExtentVector(),
extRads = extVec.radians;
let diagFrom = [0, 0],
diagTo = [1, 1];
const quad = Math.floor(extRads/(Math.PI/2));
switch(quad) {
case 1:
diagFrom = [1, 0], diagTo = [0, 1];
break;
case 2:
diagFrom = [1, 1], diagTo = [0, 0];
break;
case 3:
diagFrom = [0, 1], diagTo = [1, 0];
break;
}
const w = s.size[0] * (diagTo[0] - diagFrom[0]),
h = s.size[1] * (diagTo[1] - diagFrom[1]),
contVec = getVector([w, h]);
const hyp = contVec.length,
a = extRads - contVec.radians,
gradLine = Math.abs(Math.cos(a)) * hyp;
const cornerX = Math.cos(extRads) * gradLine,
cornerY = Math.sin(extRads) * gradLine,
corner = [cornerX/w, cornerY/h];
switch(quad) {
case 1:
corner[0] = 1 - corner[0];
break;
case 2:
corner[0] = 1 - corner[0];
corner[1] = 1 - corner[1];
break;
case 3:
corner[1] = 1 - corner[1];
break;
}
tangents.push({
start: diagFrom,
end: corner,
});
tangents.push(extend({
start: diagTo,
end: corner,
}, 1));
}
//Radial (circle or ellipse)
else {
const corner = getRelExtentCorner();
if(corner) {
const start = g.start;
corner[0] += start[0];
corner[1] += start[1];
//Vertical border:
tangents.push(extend({
start: corner,
end: [corner[0], start[1]],
}, .5));
//Horizontal border:
tangents.push(extend({
start: corner,
end: [start[0], corner[1]],
}, .5));
}
}
return tangents;
}
function normLen(len, parse = false) {
let result;
if(Array.isArray(len)) {
result = len.map(normLen);
}
else {
const num = Number(len);
if(num || (num === 0)) {
result = printLen(num * 100) + '%';
}
else {
//Probably an extent keyword
result = parse ? normLen(parseExtent(len)) : len;
}
}
//console.log('norm', parse, len, result);
return result;
}
function printLen(len) {
return len.toFixed(1).replace(/\.0+$/, '');
}
function parseExtent() {
const s = _state,
ext = s.gradient.extent;
if(typeof(ext) !== 'string') { return ext; }
const w = s.size[0],
h = s.size[1],
sX = s.gradient.start[0],
sY = s.gradient.start[1];
let x, y, distX, distY;
switch(ext) {
case 'closest-corner':
x = (sX < .5) ? 0 : 1;
y = (sY < .5) ? 0 : 1;
break;
case 'farthest-corner':
x = (sX > .5) ? 0 : 1;
y = (sY > .5) ? 0 : 1;
break;
case 'closest-side':
distX = (sX < .5) ? w * sX : w * (1-sX);
distY = (sY < .5) ? h * sY : h * (1-sY);
if(distX < distY) {
x = (sX < .5) ? 0 : 1;
y = sY;
}
else {
x = sX;
y = (sY < .5) ? 0 : 1;
}
break;
case 'farthest-side':
distX = (sX > .5) ? w * sX : w * (1-sX);
distY = (sY > .5) ? h * sY : h * (1-sY);
if(distX > distY) {
x = (sX > .5) ? 0 : 1;
y = sY;
}
else {
x = sX;
y = (sY > .5) ? 0 : 1;
}
break;
default:
throw 'Unknown extent: ' + ext;
}
return [x, y];
}
function printGradient() {
/*
radial-gradient(
[ [ circle || <length> ] [ at <position> ]? , |
[ ellipse || [ <length> | <percentage> ]{2} ] [ at <position> ]? , |
[ [ circle | ellipse ] || <extent-keyword> ] [ at <position> ]? , |
at <position> ,
]?
<color-stop> [ , <color-stop> ]+
)
where <extent-keyword> = closest-corner | closest-side | farthest-corner | farthest-side
and <color-stop> = <color> [ <percentage> | <length> ]?
*/
//SHAPE EXTENT at START, COLOR, COLOR, COLOR, ...
const s = _state,
g = s.gradient;
function printExtent() {
const ext = g.extent;
let normExt;
if(Array.isArray(ext)) {
normExt = getRelExtentCorner().map(Math.abs);
if(g.shape === 'circle') {
//circle: Single value, and not percentage:
const w = s.size[0];
normExt = printLen(normExt[0] * w) + 'px';
}
else {
//ellipse: Two values, any unit:
normExt = normLen(normExt).join(' ');
}
}
else {
//Extent keyword:
normExt = ext;
}
return normExt;
}
let grad,
stopTransform = (x) => x;
if(isLinear()) {
const vector = getExtentVector(),
degrees = vector.radians * 180/Math.PI;
grad = g.repeating ? 'repeating-linear-gradient(' : 'linear-gradient(';
grad += printLen((degrees + 90) % 360) + 'deg, ';
const [glFrom, glTo] = (function gradLineRange(gradLine) {
const ww = s.size[0],
hh = s.size[1],
glFrom = gradLine.start,
glTo = gradLine.end;
function abs(coord) {
return [coord[0]*ww, coord[1]*hh];
}
//Find where the start and extent points fall on the gradient line (p1 -> p2),
//and thus which range we need to squeeze our color stops inside.
const p1 = abs(glFrom),
p2 = abs(glTo),
gradDirX = p2[0] > p1[0],
gradDirY = p2[1] > p1[1],
gradLineLen = getLength(p1, p2);
const range = [g.start, getExtentPos()].map(p0 => {
p0 = abs(p0);
const p = pointOnLine(p1, p2, p0),
pDirX = p[0] > p1[0],
pDirY = p[1] > p1[1],
pLen = ((pDirX === gradDirX) && (pDirY === gradDirY))
? getLength(p1, p)
: -getLength(p1, p);
return (pLen/gradLineLen);
});
return range;
})(getExtentTangents()[0]);
stopTransform = (cs) => {
//https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript
const clone = JSON.parse(JSON.stringify(cs));
clone.dist = (glTo - glFrom)*cs.dist + glFrom;
return clone;
};
}
//Radial (circle or ellipse)
else {
grad = g.repeating ? 'repeating-radial-gradient(' : 'radial-gradient(';
grad += `${g.shape} ${printExtent()} at ${normLen(g.start).join(' ')}, `;
}
grad += printColorStops(g.stops.map(stopTransform));
grad += ')';
//console.log('grad', grad);
return grad;
}
function printColorStops(stops) {
stops = stops.slice()
.sort((a, b) => a.dist - b.dist);
const parts = stops.map(cs => {
//Truncate alpha values and such with a lot of decimals:
const col1 = cs.color1.replace(/(\.\d\d)\d+/g, '$1'),
doBreak = cs.color2 && !cs.smooth;
//On a non-repeating radial gradient, you can't finish with a color break at 100% (color2 won't be used).
//The max stop distance for that effect is 99.5% (at least in Chrome..)
const dist = doBreak ? Math.min(cs.dist, .995) : cs.dist;
let part = `${col1} ${normLen(dist)}`;
if(doBreak) {
const col2 = cs.color2.replace(/(\.\d\d)\d+/g, '$1');
part += `, ${col2} 0`;
}
return part;
});
return parts.join(', ');
}
function printAbsCoord(relCoord, isPct) {
return isPct ? relCoord.map(a => a*100 + '%') : relCoord.map(a => a + 'px');
}
/* UI created with Vue.js */
Vue.component('drag-point', {
props: ['p', 'isPct'],
template: '<div data-draggable @dragging="onDragging" :class="classObj" :style="styleObj"></div>',
computed: {
absCoord() {
return printAbsCoord(this.p, this.isPct);
},
classObj() {
return {
//selected: (this.p === _svgState.selectedPoint),
};
},
styleObj() {
return {
position: 'absolute',
left: this.absCoord[0],
top: this.absCoord[1],
};
},
},
methods: {
//Just relay the event up to the parent.
//Looks like the parent can't set an event handler for the original event on a component,
//e.g. <svg-point class="start" @dragging="myParentFunction" ...
onDragging(e) { this.$emit('drag_relay', e); },
}
});
Vue.component('connector', {
props: ['start', 'end'],
template: '<div class="line" :style="styleObj"></div>',
computed: {
styleObj() {
const w = _state.size[0],
h = _state.size[1],
dx = (this.end[0] - this.start[0]) * w,
dy = (this.end[1] - this.start[1]) * h,
vector = getVector([dx, dy]);
return {
position: 'absolute',
left: this.start[0]*w + 'px',
top: this.start[1]*h + 'px',
width: vector.length + 'px',
transformOrigin: 'left center',
transform: `translateY(-50%) rotate(${vector.radians}rad)`,
};
},
},
methods: {
}
});
Vue.component('color-stop', {
props: ['cs'],
template:
`<div class="stop-marker" :class="classObj" :style="styleObj"
@dragging="onDragging" @mousedown="onClick" @touchstart="onClick" @dblclick="onRemove">
<div class="marker-color-bg">
<div class="marker-color" :style="colorStyleObj"></div>
</div>
</div>`,
data: function () {
return {
//expanded: false,
}
},
computed: {
styleObj() {
const cs = this.cs;
return {
position: 'absolute',
left: cs.dist * 100 + '%',
//color: cs.color1,
};
},
classObj() {
return {
active: (this.cs === _state.gradient.currStop),
};
},
colorStyleObj() {
const cs = this.cs,
doBreak = cs.color2 && !cs.smooth;
return {
background: doBreak
? `linear-gradient(90deg, ${cs.color1} 50%, ${cs.color2} 0)`
: cs.color1,
};
},
},
methods: {
onDragging(e) {
const x = e.detail.pos[0],
slider = e.target.closest('#stops-slider');
this.cs.dist = x/slider.clientWidth;
},
onClick(e) {
const stop = _state.gradient.currStop = this.cs;
cp1.setColor(stop.color1);
cp2.setColor(stop.color2 || stop.color1);
},
onRemove(e) {
e.preventDefault();
const stops = _state.gradient.stops,
i = stops.indexOf(this.cs);
stops.splice(i, 1);
},
}
});
new Vue({
el: '#app',
data: {
state: _state,
},
computed: {
g() {
return this.state.gradient;
},
extentPoint: getExtentPos,
extentTangents: getExtentTangents,
containerStyle: function gradStyle() {
return {
width: this.state.size[0] + 'px',
height: this.state.size[1] + 'px',
};
},
gradStyle: function gradStyle() {
//Hack to refresh Chrome if only the gradient's extent has changed:
//https://bugs.chromium.org/p/chromium/issues/detail?id=775201
if(!isLinear()) {
document.querySelector('#gradient').style.backgroundImage = '';
document.querySelector('#gradient').style.backgroundImage = this.printGradient();
}
return {
backgroundImage: this.printGradient()
};
},
sliderStyle() {
var g = this.state.gradient;
return {
backgroundImage: `linear-gradient(90deg, ${printColorStops(g.stops)})`,
};
},
cssBackground() {
const bg = `background-image: ${this.printGradient()};`;
//Homebrew pretty-print:
return bg.replace('(', '(\n ')
.replace(',', ',\n ')
.replace(');', '\n);');
},
},
methods: {
printGradient,
setStart(e) {
setStart(e.detail.pos);
},
setExtent(e) {
setExtent(e.detail.pos);
},
setSize(e) {
setSize(e.detail.pos);
},
},
filters: {
prettyCompact: function(obj) {
if(!obj) return '';
const pretty = JSON.stringify(obj, null, 2),
//Collapse simple arrays (arrays without objects or nested arrays) to one line:
compact = pretty.replace(/\[[^[{]*?]/g, (match => match.replace(/\s+/g, ' ')))
return compact;
}
},
});
/*
User input.
Vue replaces the original elements, so we must wait until now to enable dragging
*/
function raiseDragEvent(dragged, pos) {
//Doesn't look like this binding is two-way,
//so we must dispatch a custom event which is handled by the point's Vue component...
// dragged.setAttribute('cx', pos[0]);
// dragged.setAttribute('cy', pos[1]);
var event = document.createEvent('CustomEvent');
event.initCustomEvent('dragging', true, false, { pos } );
//var event = new CustomEvent('dragging', { detail: { pos } });
dragged.dispatchEvent(event);
}
dragTracker({
container: document.querySelector('#grad-container'),
selector: '[data-draggable]',
handleOffset: 'center',
callback: raiseDragEvent,
});
const colorStops = document.querySelector('#stops-slider'),
cp1 = new Picker({
parent: document.querySelector('#color-picker1'),
popup: false,
onChange: function(color) {
const stop = _state.gradient.currStop;
if(stop) { stop.color1 = color.rgbaString; }
},
}),
cp2 = new Picker({
parent: document.querySelector('#color-picker2'),
popup: false,
onChange: function(color) {
const stop = _state.gradient.currStop;
if(stop) { stop.color2 = color.rgbaString; }
},
});
dragTracker({
container: colorStops,
selector: '.stop-marker',
handleOffset: 'center',
dragOutside: false,
callback: raiseDragEvent,
});
//Add color stops:
//Use a different child element to collect clicks,
//to avoid all problems with event bubbling and "drag vs click" conflicts:
colorStops.querySelector('#stops-preview').onclick = function(e) {
const stopsBounds = colorStops.getBoundingClientRect(),
x = (e.clientX - stopsBounds.left)/stopsBounds.width,
newStop = {
color1: cp1.color.rgbaString,
dist: x,
smooth: true,
color2: null,
};
_state.gradient.stops.push(newStop);
_state.gradient.currStop = newStop;
};
})();
<script src="//unpkg.com/drag-tracker@0"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.4.4/vue.min.js"></script>
<script src="//unpkg.com/vanilla-picker@2"></script>
/*
Colors by https://dribbble.com/lina_leusenko, from
https://dribbble.com/shots/3888881-Girl-With-Her-Bike
https://dribbble.com/shots/3550627-Soup-Ladies
*/
@mixin deco-border() {
//FF bug with absoulte-placed children
// outline: 1px solid gainsboro;
//Different ways to measure: clientWidth doesn't include the border, but offsetWidth does.
// border: 1px solid gainsboro;
//Consistent
box-shadow: 0 0 0 1px gainsboro;
}
$bg-transp: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='2' height='2'%3E%3Cpath d='M1,0H0V1H2V2H1' fill='lightgrey'/%3E%3C/svg%3E") left top / 20px white;
body {
display: flex;
min-height: 100vh;
margin: 0;
padding: 0;
justify-content: center;
align-items: center;
box-sizing: border-box;
font-family: Georgia, sans-serif;
//background: skyblue;
//background: #a3ecf5;
background: #00c0dd; //#0092ab;
h2 {
margin: .5em 0;
font-size: 1.5em;
text-align: center;
}
h3 {
margin: .3em 0;
font-size: 1.1em;
font-weight: normal;
text-decoration: underline;
white-space: nowrap;
}
ul {
margin: 0;
}
}
#app {
position: relative;
display: flex;
flex-flow: column nowrap;
align-items: center;
width: 100%;
padding: 1em .5em;
//Room for <pre> output:
padding-bottom: 9em;
box-sizing: border-box;
background: #e4f9fc; //#a3ecf5;
@media(min-width: 500px) {
width: auto;
max-width: calc(100vw - 3em);
padding-left: 1em;
padding-right: 1em;
margin: 1em 0;
border: 1em solid #ff8f97;
box-shadow:
inset .5em .5em 0 0 #a2020a,
inset -.5em -.5em 0 0 #ffc9cd,
.25em .25em 0 .25em #a2020a,
0 0 0 .5em #ffc9cd;
}
}
#grad-container {
position: relative;
//width: 400px;
//height: 300px;
margin: 1em 0;
@include deco-border();
background: $bg-transp;
#gradient, #controls {
display: block;
position: absolute;
top:0; left:0;
width: 100%;
height: 100%;
}
#gradient {
//background-image: radial-gradient(ellipse farthest-corner at 60px 40px, yellow 0%, red 10px, red 90px, blue 99%, transparent 100%);
}
$control-border: 2px dashed black;
.line {
height: 1.6em;
pointer-events: none;
//border-bottom: $control-border;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1000' height='7' %3E %3Cpath d='M0,3.5 H1000 l-6,-3 v6 l6,-3' fill='none' stroke='black' stroke-width='.5' stroke-dasharray='1' /%3E %3C/svg%3E");
background-size: cover;
background-position: 0;
opacity: .5;
&.arrow {
background-position: 100%;
opacity: 1;
}
}
[data-draggable] {
width: 2em;
height: 2em;
border: $control-border;
border-radius: 100%;
background: transparent;
transform: translate(-50%, -50%);
//margin: -1em;
cursor: move;
z-index: 2;
&.size {
border-radius: 0;
width: 1.5em;
height: 1.5em;
cursor: nwse-resize;
z-index: 1;
}
}
}
#settings {
position: relative;
display: flex;
input {
vertical-align: middle;
}
.tool-group {
label {
display: table;
//align-self: flex-start;
white-space: nowrap;
cursor: pointer;
h3 {
//display: inline-block;
}
}
+ .tool-group {
margin-left: 2em;
}
}
}
#color-stops {
//position: absolute;
//left:0; bottom:2em;
//width: 100%;
#stops-slider {
position: relative;
height: 20px;
margin-top: .5em;
margin-bottom: 2em;
@include deco-border();
background: $bg-transp;
cursor: copy;
}
#stops-preview {
position: absolute;
top:0;left:0;bottom:0;right:0;
}
#curr-stop {
display: flex;
align-items: center;
justify-content: center;
#color-mode {
display: flex;
flex-flow: column nowrap;
margin: 0 .3em;
label {
text-align: center;
+ label {
margin-top: 1em;
}
}
span {
display: block;
width: 2em;
height: 2em;
color: transparent;
@include deco-border();
}
#color-smooth span {
background-image: linear-gradient(90deg, white 10%, #999, black 90%);
}
#color-break span {
background-image: linear-gradient(90deg, white 50%, black 0)
}
}
}
#color-picker1, #color-picker2 {
.picker_wrapper {
margin: auto;
background: white;
}
.picker_done {
display: none;
}
@media(max-width: 700px) {
.picker_wrapper {
font-size: 1.5vw;
}
}
}
//@media(min-width: 900px) {
// column-count: 2;
// column-gap: 2.5em;
//
// margin-top: 1em;
// margin-left: 3em;
//
// #curr-stop {
// break-before: column;
// //margin-top: 1em;
// }
//}
}
.stop-marker {
top: 50%;
width: 2em;
height: 2em;
background: white;
cursor: ew-resize;
border-radius: 0 100% 100% 100%;
border: 1px solid #444;
box-sizing: border-box;
transform-origin: left top;
transform: rotate(45deg);
&.active::before {
$border: .3em;
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
margin: -$border;
//background: rgba(dodgerblue, .5);
border: $border solid rgba(dodgerblue, .5);
border-radius: 100%;
box-sizing: content-box;
}
.marker-color-bg {
position: absolute;
display: block;
width: 1.5em;
height: 1.5em;
//margin: .25em;
top:0;left:0;bottom:0;right:0;
margin: auto;
transform: rotate(-45deg);
border-radius: 100%;
background: $bg-transp;
background-size: 1em;
.marker-color {
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid #444;
border-radius: 100%;
//background: currentColor;
}
}
}
#out-css {
position: absolute;
bottom: 1em;
//max-width: 100%;
width: calc(100% - 2em);
height: 6.5em;
margin: 0;
padding: .3em .5em;
font-size: 1.2em;
white-space: pre;
@include deco-border();
background: #fefefe;
box-sizing: border-box;
}
<link href="//cdn.rawgit.com/DavidDurman/FlexiColorPicker/ed85fa3c/themes.css" rel="stylesheet" />
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment