Proefmuur met verfstreek New
A photo of an empty living room where the cursor or your finger pulls a wide brush stroke in the chosen colour across the wall; the paint sits in the wall's shading (multiply) and fades again after a few seconds. Pick a colour with the swatches (arrow keys work too) and "Whole wall" fixes the colour on the entire wall. For a painter's hero or the colour page of a paint shop.
How to use it
- Paste the HTML where the components 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.
<!-- Proefmuur met verfstreek · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie -->
<div class="jouw-container">
<div class="vpm">
<div class="vpm__kader"><div class="vpm__beeld">
<img class="vpm__foto" src="https://neddev.com/lab-media/verf-proefmuur-1.webp" alt="Lege woonkamer met een witte muur, een ladder en een verfbak" width="960" height="597" loading="lazy" draggable="false">
<div class="vpm__muur" aria-hidden="true"></div>
<canvas class="vpm__doek" aria-hidden="true"></canvas>
</div></div>
<div class="vpm__balk">
<div class="vpm__stalen" role="radiogroup" aria-label="Kies een verfkleur">
<button type="button" class="vpm__staal" role="radio" aria-checked="true" aria-label="Saliegroen" data-kleur="#8fa38a" data-naam="Saliegroen" style="--k:#8fa38a"></button>
<button type="button" class="vpm__staal" role="radio" aria-checked="false" tabindex="-1" aria-label="Okergeel" data-kleur="#d9a441" data-naam="Okergeel" style="--k:#d9a441"></button>
<button type="button" class="vpm__staal" role="radio" aria-checked="false" tabindex="-1" aria-label="Leisteen" data-kleur="#6b6f73" data-naam="Leisteen" style="--k:#6b6f73"></button>
<button type="button" class="vpm__staal" role="radio" aria-checked="false" tabindex="-1" aria-label="Oranjerood" data-kleur="#F5480F" data-naam="Oranjerood" style="--k:#F5480F"></button>
</div>
<span class="vpm__naam" aria-hidden="true">Saliegroen</span>
<button type="button" class="vpm__hele" aria-pressed="false">Hele muur</button>
</div>
</div>
</div>/* Proefmuur met verfstreek · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie */
.vpm{position:relative;width:100%;height:100%;overflow:hidden;background:#fff;font-family:Switzer,sans-serif;display:flex;flex-direction:column}
.vpm__kader{position:relative;flex:1 1 auto;min-height:0;overflow:hidden;background:#f4f3f1}
.vpm__beeld{position:absolute;left:0;top:50%;width:100%;aspect-ratio:960/597;transform:translateY(-50%)}
.vpm__foto{position:absolute;inset:0;width:100%;height:100%;user-select:none;-webkit-user-drag:none}
.vpm__muur,.vpm__doek{position:absolute;inset:0;width:100%;height:100%;mix-blend-mode:multiply;clip-path:polygon(22.6% 18.6%,88.4% 1.6%,88.4% 71.2%,22.6% 63.6%)}
.vpm__muur{background:var(--muur,transparent);opacity:0;transition:opacity .6s ease,background-color .4s}
.vpm.is-vol .vpm__muur{opacity:.9}
.vpm__doek{touch-action:pan-y;cursor:crosshair}
.vpm__balk{flex:0 0 auto;display:flex;align-items:center;gap:10px;padding:7px 12px;background:#fff;border-top:1px solid #e4e0d9}
.vpm__hele{margin-left:auto}
.vpm__stalen{display:flex;gap:5px}
.vpm__staal{width:26px;height:26px;padding:0;border:2px solid #fff;border-radius:4px;background:var(--k);box-shadow:0 0 0 1px #c9c6c0;cursor:pointer;transition:transform .15s}
.vpm__staal[aria-checked="true"]{box-shadow:0 0 0 2px #0a0a0a;transform:translateY(-2px)}
.vpm__staal:focus-visible,.vpm__hele:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vpm__naam{font-size:13px;color:#0a0a0a;min-width:72px}
.vpm__hele{font:inherit;font-size:13px;min-height:28px;padding:0 10px;border:1px solid #0a0a0a;border-radius:4px;background:#0a0a0a;color:#fff;cursor:pointer;transition:background-color .15s}
.vpm__hele:hover{background:#F5480F;border-color:#F5480F}
@media (max-width:420px){.vpm__naam{display:none}.vpm__balk{gap:6px}}
@media (prefers-reduced-motion:reduce){.vpm__muur,.vpm__staal{transition:none}}// Proefmuur met verfstreek · © 2026 NedDev BV · NedDev Lab-licentie (gratis gebruik, niet doorverkopen) · neddev.com/nl/lab/gebruik/#licentie
// root = het element waarin de HTML staat
function init(root){
const w = root.querySelector('.vpm'); if (!w) return;
const cv = w.querySelector('.vpm__doek'), ctx = cv.getContext('2d');
const stalen = [...w.querySelectorAll('.vpm__staal')], naam = w.querySelector('.vpm__naam'), hele = w.querySelector('.vpm__hele');
const rm = matchMedia('(prefers-reduced-motion: reduce)').matches;
let kleur = stalen[0].dataset.kleur, last = null, raf = 0, rust = 0, dpr = 1, W = 0, H = 0, gedemod = false;
function maat(){
const r = cv.getBoundingClientRect(); if (!r.width) return;
dpr = Math.min(2, window.devicePixelRatio || 1); W = r.width; H = r.height;
cv.width = Math.round(W * dpr); cv.height = Math.round(H * dpr); ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
}
function streek(x, y){
const a = last || { x, y }; last = { x, y };
const s = Math.max(0.8, Math.min(W, H * 1.6) / 400);
ctx.globalCompositeOperation = 'source-over'; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.strokeStyle = kleur;
ctx.globalAlpha = 0.8; ctx.lineWidth = 30 * s; ctx.beginPath(); ctx.moveTo(a.x, a.y); ctx.lineTo(x, y); ctx.stroke();
ctx.globalAlpha = 0.35; ctx.lineWidth = 4 * s;
for (let i = -2; i <= 2; i++){ const o = i * 6 * s; ctx.beginPath(); ctx.moveTo(a.x, a.y + o); ctx.lineTo(x, y + o + (Math.random() - .5) * 2); ctx.stroke(); }
ctx.globalAlpha = 1; rust = 0; start();
}
function vervaag(){
raf = 0; if (rm) return;
ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = 'rgba(0,0,0,.018)'; ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = 'source-over';
if (++rust < 420) raf = requestAnimationFrame(vervaag); else ctx.clearRect(0, 0, W, H);
}
function start(){ if (!rm && !raf) raf = requestAnimationFrame(vervaag); }
const pos = e => { const r = cv.getBoundingClientRect(); return { x: e.clientX - r.left, y: e.clientY - r.top }; };
let down = false;
cv.addEventListener('pointerdown', e => { down = true; last = null; const p = pos(e); streek(p.x, p.y); });
cv.addEventListener('pointermove', e => { if (e.pointerType !== 'mouse' && !down) return; const p = pos(e); streek(p.x, p.y); });
cv.addEventListener('pointerleave', () => { last = null; });
const stop = () => { down = false; last = null; };
cv.addEventListener('pointerup', stop); cv.addEventListener('pointercancel', stop);
function kies(i, focus){
stalen.forEach((s, j) => { s.setAttribute('aria-checked', String(i === j)); s.tabIndex = i === j ? 0 : -1; });
kleur = stalen[i].dataset.kleur; naam.textContent = stalen[i].dataset.naam;
if (w.classList.contains('is-vol')) w.style.setProperty('--muur', kleur);
if (focus) stalen[i].focus();
}
stalen.forEach((s, i) => {
s.addEventListener('click', () => kies(i));
s.addEventListener('keydown', e => {
const d = { ArrowRight:1, ArrowDown:1, ArrowLeft:-1, ArrowUp:-1 }[e.key]; if (!d) return;
e.preventDefault(); kies((i + d + stalen.length) % stalen.length, true);
});
});
hele.addEventListener('click', () => {
const op = !w.classList.contains('is-vol'); w.style.setProperty('--muur', kleur);
w.classList.toggle('is-vol', op); hele.setAttribute('aria-pressed', String(op)); hele.textContent = op ? 'Muur wit' : 'Hele muur';
});
function demo(){
if (gedemod || rm || !W) return; gedemod = true; last = null; const t0 = performance.now();
(function stap(t){
const k = Math.min(1, (t - t0) / 1100);
streek(W * (0.3 + 0.5 * k), H * (0.36 + 0.1 * Math.sin(k * Math.PI * 2.4)));
if (k < 1) requestAnimationFrame(stap); else last = null;
})(t0);
}
maat();
if ('ResizeObserver' in window) new ResizeObserver(maat).observe(cv);
if ('IntersectionObserver' in window) new IntersectionObserver(es => { if (es[0].isIntersecting) setTimeout(demo, 400); }, { threshold: 0.5 }).observe(w);
}
init(document.querySelector('.jouw-container'));
More components
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 →