Vacatures beoordelen door te slepen New
A stack of job cards: drag right to save and left to skip, or use the buttons or the arrow keys. With a stamp that follows the drag, undo, a counter via aria-live and an end screen listing the saved jobs. For a careers site or a staffing agency that lets candidates choose quickly.
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.
<!-- Vacatures beoordelen door te slepen · NedDev · eigen werk. -->
<div class="jouw-container">
<div class="vsk">
<div class="vsk__deck"></div>
<div class="vsk__bar">
<button class="vsk__b vsk__no" type="button">Overslaan</button>
<p class="vsk__st" aria-live="polite">0 bewaard</p>
<button class="vsk__b vsk__undo" type="button" disabled>Terug</button>
<button class="vsk__b vsk__yes" type="button">Bewaren</button>
</div>
</div>
</div>/* Vacatures beoordelen door te slepen · NedDev · eigen werk. */
.vsk{position:absolute;inset:0;display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px;font-family:'Switzer',system-ui,sans-serif;color:#0a0a0a;background:#f4f3f1;text-align:left}
.vsk *{box-sizing:border-box}
.vsk__deck{position:relative;flex:1;min-height:0;max-height:170px;width:min(100%,330px);margin:auto auto 0}
.vsk__card{position:absolute;inset:0;padding:12px 14px;background:#fff;border:1px solid rgba(10,10,10,.14);border-radius:8px;display:flex;flex-direction:column;gap:3px;overflow:hidden;touch-action:pan-y;user-select:none;cursor:grab;transition:transform .3s cubic-bezier(.2,.8,.2,1),opacity .3s;box-shadow:0 6px 16px -10px rgba(10,10,10,.35)}
.vsk__card.is-drag{transition:none;cursor:grabbing}
.vsk__card:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
.vsk__t{margin:0;font-size:16px;line-height:1.2;font-weight:700;letter-spacing:-.01em;padding-right:76px}
.vsk__o{margin:0;font-size:12.5px;color:#4a4a4a}
.vsk__s{margin:6px 0 0;font-size:13px;font-weight:600;font-variant-numeric:tabular-nums}
.vsk__p{margin:0;font-size:12px;color:#4a4a4a;line-height:1.35}
.vsk__stamp{position:absolute;top:12px;right:12px;padding:3px 7px;border:2px solid currentColor;border-radius:4px;font-size:12px;font-weight:700;opacity:0;transform:rotate(8deg);pointer-events:none}
.vsk__stamp--ja{color:#c43a0b}
.vsk__stamp--nee{color:#0a0a0a;transform:rotate(-8deg)}
.vsk__end{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:6px;padding:12px 14px;background:#fff;border:1px solid rgba(10,10,10,.14);border-radius:8px;font-size:13px}
.vsk__end p{margin:0}
.vsk__end strong{font-size:15px}
.vsk__end button{align-self:flex-start;margin-top:4px}
.vsk__bar{display:flex;align-items:center;gap:6px;width:min(100%,330px);margin:0 auto auto}
.vsk__st{flex:1;margin:0;font-size:12px;color:#4a4a4a;text-align:center}
.vsk__b{min-height:32px;padding:0 11px;border:1px solid #0a0a0a;border-radius:4px;background:#fff;color:#0a0a0a;font:600 12.5px/1 'Switzer',system-ui,sans-serif;cursor:pointer;transition:background-color .15s,color .15s}
.vsk__b:hover:not(:disabled){background:#0a0a0a;color:#fff}
.vsk__yes{background:#F5480F;border-color:#F5480F}
.vsk__yes:hover:not(:disabled){background:#0a0a0a;border-color:#0a0a0a}
.vsk__b:disabled{opacity:.4;cursor:default}
.vsk__b:focus-visible{outline:2px solid #F5480F;outline-offset:2px}
@media (prefers-reduced-motion:reduce){.vsk__card{transition:none}}// Vacatures beoordelen door te slepen · NedDev · eigen werk.
// root = het element waarin de HTML staat
function init(root){
var w = root.querySelector('.vsk'), deck = w.querySelector('.vsk__deck'), st = w.querySelector('.vsk__st');
var bNo = w.querySelector('.vsk__no'), bYes = w.querySelector('.vsk__yes'), bUndo = w.querySelector('.vsk__undo');
var rm = matchMedia('(prefers-reduced-motion: reduce)').matches;
var D = [
['Servicemonteur warmtepompen', 'Van Dijk Installatie, Zwolle', '€ 3.100 tot € 3.900 · 40 uur', 'Eigen bedrijfsbus en opleiding F-gassen'],
['Planner binnendienst', 'Hendriks Bouw, Nijmegen', '€ 2.900 tot € 3.500 · 32 tot 40 uur', 'Vrijdag thuiswerken, vast contract na een jaar'],
['Doktersassistent', 'Huisartsenpraktijk De Linde, Amersfoort', '€ 2.600 tot € 3.300 · 28 uur', 'Geen avonddiensten, eigen spreekuur'],
['Junior accountmanager', 'Drukkerij Peeters, Tilburg', '€ 3.000 tot € 3.600 · 40 uur', 'Leaseauto na de proeftijd'],
['Medewerker werkplaats', 'Rijwielhuis Spaarne, Haarlem', '€ 2.500 tot € 2.900 · 24 uur', 'Fiets van de zaak, zaterdag om de week']
];
var idx = 0, hist = [], kept = [];
function esc(s) { return s.replace(/&/g, '&').replace(/</g, '<'); }
function count() { st.textContent = kept.length + ' bewaard'; bUndo.disabled = !hist.length; bNo.disabled = bYes.disabled = idx >= D.length; }
function render(focus) {
deck.innerHTML = '';
if (idx >= D.length) {
var e = document.createElement('div'); e.className = 'vsk__end';
e.innerHTML = '<p><strong>Je hebt alle ' + D.length + ' vacatures bekeken.</strong></p><p>' + (kept.length ? 'Bewaard: ' + kept.map(function (i) { return esc(D[i][0]); }).join(', ') + '.' : 'Je hebt niets bewaard.') + '</p><button class="vsk__b vsk__again" type="button">Opnieuw bekijken</button>';
deck.appendChild(e);
e.querySelector('.vsk__again').addEventListener('click', function () { idx = 0; hist = []; kept = []; render(true); count(); });
if (focus) e.querySelector('button').focus();
return;
}
for (var k = Math.min(D.length - 1, idx + 2); k >= idx; k--) {
var d = D[k], c = document.createElement('div'), depth = k - idx;
c.className = 'vsk__card';
c.innerHTML = '<h3 class="vsk__t">' + esc(d[0]) + '</h3><p class="vsk__o">' + esc(d[1]) + '</p><p class="vsk__s">' + esc(d[2]) + '</p><p class="vsk__p">' + esc(d[3]) + '</p><span class="vsk__stamp vsk__stamp--ja" aria-hidden="true">Bewaren</span><span class="vsk__stamp vsk__stamp--nee" aria-hidden="true">Overslaan</span>';
c.style.transform = 'translateY(' + (-depth * 6) + 'px) scale(' + (1 - depth * .04) + ')';
c.style.zIndex = 10 - depth;
if (depth) { c.setAttribute('aria-hidden', 'true'); c.style.opacity = depth > 1 ? .5 : .85; }
else { c.tabIndex = 0; c.setAttribute('role', 'group'); c.setAttribute('aria-label', 'Vacature ' + (k + 1) + ' van ' + D.length + ': ' + d[0] + '. Pijl rechts bewaart, pijl links slaat over.'); bind(c); }
deck.appendChild(c);
}
if (focus) deck.lastChild.focus();
}
function decide(dir) {
if (idx >= D.length) return;
var c = deck.lastChild, had = document.activeElement === c || w.contains(document.activeElement);
hist.push(dir); if (dir > 0) kept.push(idx);
c.style.transition = rm ? 'none' : '';
c.style.transform = 'translateX(' + (dir * 130) + '%) rotate(' + (dir * 14) + 'deg)'; c.style.opacity = 0;
setTimeout(function () { idx++; render(had && document.activeElement !== bNo && document.activeElement !== bYes); count(); }, rm ? 0 : 260);
}
function bind(c) {
var x0 = 0, y0 = 0, dx = 0, on = false, ja = c.querySelector('.vsk__stamp--ja'), nee = c.querySelector('.vsk__stamp--nee');
c.addEventListener('pointerdown', function (e) { if (e.button) return; on = true; x0 = e.clientX; y0 = e.clientY; dx = 0; c.setPointerCapture(e.pointerId); c.classList.add('is-drag'); });
c.addEventListener('pointermove', function (e) {
if (!on) return; dx = e.clientX - x0; var dy = (e.clientY - y0) * .2;
c.style.transform = 'translate(' + dx + 'px,' + dy + 'px) rotate(' + (dx / 22) + 'deg)';
ja.style.opacity = Math.max(0, Math.min(1, dx / 80)); nee.style.opacity = Math.max(0, Math.min(1, -dx / 80));
});
function up() { if (!on) return; on = false; c.classList.remove('is-drag'); if (Math.abs(dx) > 80) decide(dx > 0 ? 1 : -1); else { c.style.transform = ''; ja.style.opacity = nee.style.opacity = 0; } }
c.addEventListener('pointerup', up); c.addEventListener('pointercancel', up);
c.addEventListener('keydown', function (e) { if (e.key === 'ArrowRight') { e.preventDefault(); decide(1); } else if (e.key === 'ArrowLeft') { e.preventDefault(); decide(-1); } });
}
bNo.addEventListener('click', function () { decide(-1); });
bYes.addEventListener('click', function () { decide(1); });
bUndo.addEventListener('click', function () { if (!hist.length) return; var d = hist.pop(); idx--; if (d > 0) kept.pop(); render(false); count(); });
render(false); count();
}
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 →