<div class="col">
	<div class="controls">
		<h1>Zig-zag gradient lab</h1>
		<h3>Options</h3>
		<div class="controls__group">
		<label for="#angle">Angle</label>
			<input type="range" min="5" max="85" id="angle" data-input="range">
		</div>
		<div class="controls__group">
			<label for="#thickness">Stripe thickness</label>
			<input type="range" min="2" max="120" step="1" id="thickness" data-input="thickness" value="30">
		</div>
		<div class="controls__group">
			<label for="#width">Segment width</label>
			<input type="range" min="10" max="200" step="1" id="width" data-input="width" value="100">
		</div>
		<div class="controls__group">
			<label for="#color1">Primary colour</label>
			<input type="range" min="0" max="360" step="1" id="color1" data-input="color1" value="100">
		</div>
		<div class="controls__group">
			<label for="#color1">Contrast</label>
			<input type="range" min="50" max="90" step="1" id="contrast" data-input="contrast">
		</div>

		<h3>CSS output</h3>
		<div class="css" data-output></div>
	</div>
</div>

<div class="bg" data-bg></div>
@import url("https://fonts.googleapis.com/css?family=Open+Sans:400,700");

* {
	box-sizing: border-box;
}

body {
	font-family: "Open Sans", sans-serif;
	margin: 0;
	
	@media (min-width: 40em) {
		display: flex;
	}
}

.bg {
	--h1: 100deg;
	--h2: calc(var(--h1) + calc(180deg - 30deg));
	--h3: calc(var(--h1) + calc(180deg + 30deg));
	--l1: 50%;
	--c1: hsl(var(--h1, 0), 90%, var(--l1));
	--c2: hsl(var(--h2), 90%, var(--l, 70%));
	--c3: hsl(var(--h3), 60%, var(--d, 40%));
	--t: 10px;
	--w: 100px;
	--angle: 45deg;
	--grad: var(--c1) var(--t), var(--c1) calc(var(--t) * 2), var(--c2) calc(var(--t) * 2), var(--c2) calc(var(--t) * 3), var(--c3) calc(var(--t) * 3), var(--c3) calc(var(--t) * 4);
	--mask: repeating-linear-gradient(to right, black var(--w), black calc(var(--w) * 2), transparent calc(var(--w) * 2), transparent calc(var(--w) * 3));
	background: repeating-linear-gradient(var(--angle), var(--grad)), var(--c1);
	background-size: var(--w) 100%;
	height: 100vh;
	position: relative;
	flex: 1 0 auto;
	transition: --angle 200ms, --t 200ms, --w 200ms;
}

.bg::after {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	content: '';
	background: repeating-linear-gradient(calc(var(--angle) * -1), var(--grad)), var(--c1);
	background-size: var(--w) 100%;
	-webkit-mask-image: var(--mask);
	mask-image: var(--mask);
}

.controls {
	padding: 1rem;
	background-color: white;
	max-width: 30rem;
	z-index: 1;
	box-shadow: 0 0 0.75rem rgba(0, 0, 0, 0.3);
	
	@media (min-width: 40em) {
		display: inline-block;
		position: fixed;
		overflow: scroll;
		top: 0;
		left: 0;
		height: 100%;
	}
}

pre {
	max-width: 100%;
	overflow: scroll;
	background: rgba(240, 240, 240, 1);
	padding: 1rem;
	border-radius: 0.2rem;
}

.controls__group {
	display: flex;
	align-items: center;
	max-width: 20rem;
	
	label {
		margin-right: 1rem;
	}
	
	input {
		margin-left: auto;
	}
}
View Compiled
const angleInput = document.querySelector('[data-input="range"]')
const thicknessInput = document.querySelector('[data-input="thickness"]')
const widthInput = document.querySelector('[data-input="width"]')
const colorInput = document.querySelector('[data-input="color1"]')
const contrastInput = document.querySelector('[data-input="contrast"]')
const outputContainer = document.querySelector('[data-output]')
const bg = document.querySelector('[data-bg]')
const inputs = [...document.querySelectorAll('[data-input]')]

const angleChange = (target) => {
	const newAngle = `${target.value}deg`
	setItem('--angle', newAngle, 'angle', target.value)
}

const thicknessChange = (target) => {
	const newThickness = `${target.value}px`
	setItem('--t', newThickness, 'thickness', target.value)
}

const setItem = (cssVar, newValue, property, unitlessValue) => {
	bg.style.setProperty(cssVar, newValue)
	getCSSOutput()
	localStorage.setItem(property, unitlessValue)
}

const widthChange = (target) => {
	const newWidth = `${target.value}px`
	setItem('--w', newWidth, 'width', target.value)
}

const colorChange = (target) => {
	const newColor = `${target.value}deg`
	setItem('--h1', newColor, 'color', target.value)
}

const setContrast = (contrast) => {
	const newL = `${contrast}%`
	const newD = `${100 - contrast}%`
	bg.style.setProperty('--l', newL)
	bg.style.setProperty('--d', newD)
}

const contrastChange = (target) => {
	setContrast(target.value)
	getCSSOutput()
	localStorage.setItem('contrast', target.value)
}

const getCSSOutput = () => {
	const { backgroundImage, backgroundSize } = getComputedStyle(bg)
	const backgroundImageAfter = getComputedStyle(bg, '::after').backgroundImage
	const mask = getComputedStyle(bg, '::after').webkitMaskImage
	const cssOutput = `
<pre>
.bg {
	background-image: ${backgroundImage};
	background-size: ${backgroundSize};
}

.bg::after {
	background-image: ${backgroundImageAfter};
	background-size: ${backgroundSize};
	-webkit-mask-image: ${mask};
	mask-image: ${mask};
}
</pre>`
	
	outputContainer.innerHTML = cssOutput
}

const setStyle = (input, property, unitlessValue, value) => {
	if (unitlessValue) {
		input.value = unitlessValue
		bg.style.setProperty(property, value)
	}
}

const getInitialStateFromLocalStorage = () => {
	const angle = localStorage.getItem('angle')
	const thickness = localStorage.getItem('thickness')
	const width = localStorage.getItem('width')
	const color = localStorage.getItem('color')
	const contrast = localStorage.getItem('contrast')
	
	setStyle(angleInput, '--angle', angle, `${angle}deg`)
	setStyle(thicknessInput, '--t', thickness,  `${thickness}px`)
	setStyle(widthInput, '--w', width, `${width}px`)
	setStyle(colorInput, '--h1', color, `${color}deg`)
	
	if (contrast) {
		setContrast(contrast)
		contrastInput.value = contrast
	}
}

getInitialStateFromLocalStorage()
getCSSOutput()

let activeInput

const handleMouseDown = (e) => {
	activeInput = e.target
}

const updateValues = (target) => {
	const { id } = target
	
	if (id === 'angle') {
		angleChange(target)
	}
	
	if (id === 'thickness') {
		thicknessChange(target)
	}
	
	if (id === 'color1') {
		colorChange(target)
	}
	
	if (id === 'width') {
		widthChange(target)
	}
	
	if (id === 'contrast') {
		contrastChange(target)
	}
}

const handleMouseMove = (e) => {
	if (activeInput === e.target) {
		updateValues(e.target)
	}
}

const handleChange = (e) => {
	updateValues(e.target)
}

inputs.forEach(el => {
	el.addEventListener('change', handleChange)
	el.addEventListener('mousedown', handleMouseDown)
	el.addEventListener('mousemove', _.throttle(handleMouseMove, 100))
	updateValues(el)
})

CSS.registerProperty({
  name: '--angle',
  syntax: '<angle>',
  inherits: true,
  initialValue: `${angleInput.value}deg`,
})

CSS.registerProperty({
  name: '--t',
  syntax: '<length>',
  inherits: true,
  initialValue: `${thicknessInput.value}px`,
})

CSS.registerProperty({
  name: '--w',
  syntax: '<length>',
  inherits: true,
  initialValue: `${widthInput.value}px`,
})

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.19/lodash.min.js