Halftone CMYK New
Splits an image into four rotated print screens (cyan, magenta, yellow, black) like an offset press. The slider sets the screen size; strong for printers, magazines and editorial sites.
C 15° · M 75° · Y 0° · K 45°
How to use it
- Paste the HTML where the backgrounds & webgl should appear.
- Add the CSS to your stylesheet. All classes are prefixed, so nothing clashes with your own styles.
- Add the JavaScript and call init() with the element that contains the HTML.
<!-- Halftone CMYK · Bron: Paper Shaders · licentie Apache-2.0. Laat deze vermelding staan bij hergebruik. -->
<div class="jouw-container">
<div class="htc"><div class="htc__fb"></div><canvas class="htc__gl" aria-hidden="true"></canvas><span class="htc__tag">C 15° · M 75° · Y 0° · K 45°</span><label class="htc__ctl">Raster <input type="range" min="4" max="16" step="1" value="7" aria-label="Rastergrootte"></label></div>
</div>/* Halftone CMYK · Bron: Paper Shaders · licentie Apache-2.0. Laat deze vermelding staan bij hergebruik. */
.htc{position:absolute;inset:0;overflow:hidden;background:#f4f3f1;font-family:'Switzer',system-ui,sans-serif}
.htc__gl,.htc__fb,.htc__fb canvas{position:absolute;inset:0;width:100%;height:100%;display:block}
.htc.is-nogl .htc__gl{display:none}
.htc__tag{position:absolute;left:12px;bottom:12px;z-index:3;font:500 10px/1 'JetBrains Mono',monospace;letter-spacing:.14em;text-transform:uppercase;background:#0a0a0a;color:#fff;padding:6px 8px;border-radius:3px}
.htc__ctl{position:absolute;right:12px;bottom:12px;z-index:3;display:flex;align-items:center;gap:8px;background:#fff;border:1px solid rgba(10,10,10,.14);border-radius:4px;padding:6px 9px;font:500 10px/1 'JetBrains Mono',monospace;letter-spacing:.1em;text-transform:uppercase;color:#0a0a0a}
.htc__ctl input{width:84px;accent-color:#F5480F;margin:0}
.htc.is-nogl .htc__ctl{display:none}
.htc__fb::before,.htc__fb::after{content:'';position:absolute;inset:0;z-index:1;mix-blend-mode:multiply}
.htc__fb::before{background:radial-gradient(circle,rgba(0,168,224,.32) 1.6px,transparent 2px) 0 0/7px 7px;transform:rotate(15deg) scale(1.5)}
.htc__fb::after{background:radial-gradient(circle,rgba(236,26,128,.26) 1.6px,transparent 2px) 2px 3px/7px 7px;transform:rotate(-15deg) scale(1.5)}// Halftone CMYK · Bron: Paper Shaders · licentie Apache-2.0. Laat deze vermelding staan bij hergebruik.
// root = het element waarin de HTML staat
function init(root){
function mkGL(cv, frag) {
var gl = null;
try { gl = cv.getContext('webgl', { antialias: false, premultipliedAlpha: false }) || cv.getContext('experimental-webgl'); } catch (e) { gl = null; }
if (!gl) return null;
function sh(t, s) { var o = gl.createShader(t); gl.shaderSource(o, s); gl.compileShader(o); if (!gl.getShaderParameter(o, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(o) || 'shader'); return o; }
var pr = gl.createProgram();
try {
gl.attachShader(pr, sh(gl.VERTEX_SHADER, 'attribute vec2 p;varying vec2 vUv;void main(){vUv=p*.5+.5;gl_Position=vec4(p,0.,1.);}'));
gl.attachShader(pr, sh(gl.FRAGMENT_SHADER, 'precision highp float;varying vec2 vUv;' + frag));
gl.linkProgram(pr);
if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error('link');
} catch (e) { console.warn('lab webgl', e); return null; }
gl.useProgram(pr);
var buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
var loc = gl.getAttribLocation(pr, 'p'); gl.enableVertexAttribArray(loc); gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
var U = {};
var api = {
gl: gl,
u: function (n) { if (!(n in U)) U[n] = gl.getUniformLocation(pr, n); return U[n]; },
upd: function (t, src, unit) {
gl.activeTexture(gl.TEXTURE0 + unit); gl.bindTexture(gl.TEXTURE_2D, t);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, src);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
},
tex: function (src, unit, name) { var t = gl.createTexture(); api.upd(t, src, unit); gl.uniform1i(api.u(name), unit); return t; },
fit: function () {
var d = Math.min(window.devicePixelRatio || 1, 2), w = Math.max(1, Math.round(cv.clientWidth * d)), h = Math.max(1, Math.round(cv.clientHeight * d));
if (cv.width !== w || cv.height !== h) { cv.width = w; cv.height = h; }
gl.viewport(0, 0, w, h); gl.uniform2f(api.u('uRes'), w, h); return d;
},
draw: function () { gl.drawArrays(gl.TRIANGLES, 0, 3); }
};
return api;
}
function runLoop(el, frame, still) {
var reduce = matchMedia('(prefers-reduced-motion:reduce)').matches, vis = true, raf = 0, t0 = performance.now();
function tick(now) { raf = 0; frame((now - t0) / 1000); if (!reduce && !still && vis) raf = requestAnimationFrame(tick); }
if ('IntersectionObserver' in window) new IntersectionObserver(function (es) { vis = es[0].isIntersecting; if (vis && !raf) raf = requestAnimationFrame(tick); }).observe(el);
raf = requestAnimationFrame(tick);
return { reduce: reduce, kick: function () { if (!raf) raf = requestAnimationFrame(tick); } };
}
function paintScene(c, v) {
var x = c.getContext('2d'), W = c.width, H = c.height, night = v === 1;
var bg = x.createLinearGradient(0, 0, 0, H);
if (night) { bg.addColorStop(0, '#0a0a0a'); bg.addColorStop(1, '#2b1b12'); } else { bg.addColorStop(0, '#f4f3f1'); bg.addColorStop(1, '#ddd5c9'); }
x.fillStyle = bg; x.fillRect(0, 0, W, H);
var sx = W * .68, sy = H * .44, sr = H * .27;
var sg = x.createRadialGradient(sx - sr * .35, sy - sr * .35, sr * .1, sx, sy, sr);
if (night) { sg.addColorStop(0, '#ffffff'); sg.addColorStop(1, '#cfc8bd'); } else { sg.addColorStop(0, '#ffa071'); sg.addColorStop(1, '#F5480F'); }
x.fillStyle = sg; x.beginPath(); x.arc(sx, sy, sr, 0, 7); x.fill();
x.fillStyle = night ? '#7a2a0c' : '#6b645d';
x.beginPath(); x.moveTo(0, H * .72); x.bezierCurveTo(W * .25, H * .58, W * .45, H * .78, W * .7, H * .66); x.bezierCurveTo(W * .85, H * .6, W * .95, H * .66, W, H * .64); x.lineTo(W, H); x.lineTo(0, H); x.fill();
x.fillStyle = night ? '#F5480F' : '#0a0a0a';
x.beginPath(); x.moveTo(0, H * .84); x.bezierCurveTo(W * .3, H * .74, W * .6, H * .92, W, H * .8); x.lineTo(W, H); x.lineTo(0, H); x.fill();
x.strokeStyle = night ? '#f4f3f1' : '#0a0a0a'; x.lineWidth = H * .012;
x.beginPath(); x.moveTo(W * .2, H * .8); x.lineTo(W * .2, H * .34); x.lineTo(W * .44, H * .34); x.moveTo(W * .16, H * .34); x.lineTo(W * .2, H * .34); x.moveTo(W * .4, H * .34); x.lineTo(W * .4, H * .5); x.stroke();
x.fillStyle = night ? '#f4f3f1' : '#0a0a0a';
x.fillRect(W * .385, H * .5, W * .03, H * .05);
x.textBaseline = 'top';
x.font = '400 ' + Math.round(H * .08) + "px Boldonse, Switzer, sans-serif"; x.fillText('HAVENKADE', W * .06, H * .08);
x.font = '500 ' + Math.round(H * .034) + "px 'JetBrains Mono', monospace"; x.fillText(night ? 'NACHTVERSIE · ROTTERDAM' : 'DAGVERSIE · ROTTERDAM', W * .06, H * .22);
}
function fontsThen(cb) {
if (!document.fonts || !document.fonts.load) return;
Promise.all(['400 40px Boldonse', "500 20px 'JetBrains Mono'", '600 20px Switzer'].map(function (f) { return document.fonts.load(f).catch(function () {}); })).then(cb);
}
var w = root.querySelector('.htc'); if (!w) return;
var fb = w.querySelector('.htc__fb'), cv = w.querySelector('.htc__gl'), rng = w.querySelector('input');
var sc = document.createElement('canvas'); sc.width = 960; sc.height = 600; paintScene(sc, 0); fb.appendChild(sc);
var g = mkGL(cv, `uniform sampler2D uTex;uniform vec2 uRes;uniform float uCell;
vec2 rot(vec2 p,float a){float c=cos(a),s=sin(a);return vec2(c*p.x-s*p.y,s*p.x+c*p.y);}
vec4 cmyk(vec3 c){float k=1.-max(max(c.r,c.g),c.b);return vec4((1.-c-k)/max(1.-k,.001),k);}
float ink(vec2 fc,float a,int ch){
vec2 q=rot(fc,a)/uCell;vec2 cc=floor(q)+.5;vec2 pc=rot(cc*uCell,-a);
vec4 v=cmyk(texture2D(uTex,clamp(pc/uRes,0.,1.)).rgb);
float val=ch==0?v.x:(ch==1?v.y:(ch==2?v.z:v.w));
float r=sqrt(clamp(val,0.,1.))*.64;float aa=1./uCell;
return (1.-smoothstep(r-aa,r+aa,length(q-cc)))*smoothstep(.03,.12,val);
}
void main(){
vec2 fc=gl_FragCoord.xy;
float c=ink(fc,.2618,0),m=ink(fc,1.309,1),y=ink(fc,0.,2),k=ink(fc,.7854,3);
vec3 col=vec3(.985,.978,.965);
col*=mix(vec3(1.),vec3(0.,.66,.88),c*.9);
col*=mix(vec3(1.),vec3(.93,.1,.5),m*.88);
col*=mix(vec3(1.),vec3(1.,.9,0.),y*.95);
col*=mix(vec3(1.),vec3(.06),k);
gl_FragColor=vec4(col,1.);
}`);
if (!g) { w.classList.add('is-nogl'); return; }
var tx = g.tex(sc, 0, 'uTex'), L;
rng.addEventListener('input', function () { L.kick(); });
L = runLoop(w, function () { var d = g.fit(); g.gl.uniform1f(g.u('uCell'), +rng.value * d); g.draw(); }, true);
if ('ResizeObserver' in window) new ResizeObserver(function () { L.kick(); }).observe(w);
fontsThen(function () { paintScene(sc, 0); g.upd(tx, sc, 0); L.kick(); });
}
init(document.querySelector('.jouw-container'));
More backgrounds & webgl
Seen something you like?
Add it to your selection and send it over. We build your site around it, in your brand.
Get in touch →