Ei teneste av

Føx er webutviklingsgreina til Kvåle Solutions. Føx spesialiserer seg på å lage lette, effektive og semantiske nettsider. Ei Føx-nettside er som ein rev: lett, kjapp og lur.

Å lage nettstad involverer mykje meir enn berre programmering. Viss du bruker Føx, treng du ikkje reise langt for å finne dei andre tenestene du behøver. Kvåle Solutions har fleire andre avdelingar og tilbyr grafisk design, typesetting og språktenester. Ditt prosjekt kan verte heilt fullført av Kvåle Solutions med berre eitt fast kontaktpunkt (SPOC).

Føx laga nettstaden du er på akkurat no. Du kan gjerne utforske han, og om du er interessert i prosessen bak skapinga, kan du rulle ned for å lese utviklaren si oppsummering.


Bygginga av Kvåle Solutions-nettstaden

Nettstaden til Kvåle Solutions er ein komplett graf; kvar side kan nåast frå kvar som helst anna side. Dette designet reflekterer samankoplinga av dei fire hovudtenestene, og gjer det enkelt for kundar å navigere på nettstaden.

INDEX

Sida er tilgjengeleg på engelsk, bokmål og nynorsk, noko som blir handtert ved bruk av underkatalogane /nb/ og /nn/.

Me valde ein litt ukonvensjonell framgangsmåte med PC-versjonen til framsida. Me ønskte at kvart kort skulle vere stort og synleg, og me ønskte at sida skulle vere ikkje-rullbar. Det var umogleg å møte begge desse kriteria om me samstundes skulle ha hovudlogoen som overskrift. Så... me sette logoen i midten i staden. Ei slik ukonvensjonell løysing kan kanskje forfjamsa nokre kundar, men det er ein risiko verdt å ta. Me lever i ein merksemdsøkonomi, og om du kombinerer ukonvensjonalitet med høg kvalitet, så vinn du merksemdskonkurransen.

Mobilversjonen til framsida er derimot ei rullbar side med eit normalt oppsett, då storleiken gjorde den andre løysinga suboptimal. Me tek alltid omsyn til ulike medium og utformar sidene våre deretter. Me held ein konsekvent stil på tvers av mediuma, men me lèt aldri avgrensingane ved eitt medium halde oss tilbake i eit anna.

Kvart einaste bilete på tvers av nettstaden er ein .svg, takka vere Æxis og TeXtract som har forsynt nettstaden med sin vektorbaserte grafikk.

Den mest teknisk avanserte sida er den som tilhøyrer Aexis, som viser fram ei Bézier-kurve som bøyer seg som ein funksjon av den besøkande si rulling ned sida.

bezier_scroll.js
     
const track = document.querySelector('.animation-track');

if (track) {
    const mainCurve = document.getElementById('mainCurve');
    const dash1 = document.getElementById('dash1');
    const dash2 = document.getElementById('dash2');
    const cp1 = document.getElementById('cp1');
    const cp2 = document.getElementById('cp2');

    let ticking = false;

    const updateCurve = () => {
        const rect = track.getBoundingClientRect();

        // Eg fann desse to gjennom finjustering. Eg syns dei gjev ein god balanse mellom å la kurva bøye seg, 
        // og å ikkje ta for mykje tid.
        const progress = Math.max(0, (window.innerHeight / 2) - rect.top);
        const baseMove = Math.min(400, progress * 0.8);

        // Sidan eg ville at heile kurva og styringspunkta skulle vere synlege på slutten,
        // så var eg meir avgrensa vertikalt enn horisontalt,
        // så eg gjorde slik at styringspunkta bevegde seg meir horisontalt enn vertikalt
        const moveY = baseMove * 0.55;
        const moveX = baseMove * 2;
        
        // Eg fekk dei til å bevege seg i motsatte retningar for den mest dramatiske effekten
        const newCy1 = 250 - moveY;
        const newCy2 = 250 + moveY;
        const newCx1 = 400 + moveX;
        const newCx2 = 600 - moveX;

        cp1.setAttribute('cx', newCx1);
        cp1.setAttribute('cy', newCy1);
        cp2.setAttribute('cx', newCx2);
        cp2.setAttribute('cy', newCy2);
        dash1.setAttribute('x2', newCx1);
        dash1.setAttribute('y2', newCy1);
        dash2.setAttribute('x2', newCx2);
        dash2.setAttribute('y2', newCy2);
        mainCurve.setAttribute('d', `M 100 250 C ${newCx1} ${newCy1}, ${newCx2} ${newCy2}, 900 250`);

        ticking = false;
    };

    const onScroll = () => {
        if (!ticking) {
            window.requestAnimationFrame(updateCurve);
            ticking = true;
        }
    };

    const observerOptions = {
        root: null,
        rootMargin: '100px',
        threshold: 0
    };

    const observer = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                window.addEventListener('scroll', onScroll, { passive: true });
            } else {
                window.removeEventListener('scroll', onScroll);
            }
        });
    }, observerOptions);

    observer.observe(track);
}
                        

Aexis har òg ein 2 699x zoom inn i eit LaTeX -generert .svg-bilete av ein Fibonacci-mosaikk, heilt koda i vanilla JS.

fibonacci_zoom.js
         
const zoomBtn = document.getElementById('zoom-trigger-btn');

if (zoomBtn) {
    const fibSvg = document.getElementById('fibonacci-svg');
    
    // Koordinatane av det inste kvadratet vart funne ved bruk av DevTools.
    const fStart = { x: 12.79, y: 12.04, w: 539.86, h: 321.62 };
    const fTarget = { x: 387.571, y: 244.817 };
    const startCenter = { x: fStart.x + (fStart.w / 2), y: fStart.y + (fStart.h / 2) };

    const fEndWidth = 0.2;
    const fDuration = 17000; 
    const fPauseTime = 2500;
    const fEndHeight = (fEndWidth * fStart.h) / fStart.w;

    let fStartTime = null;
    let isZooming = false;

    const formatX = (num) => Math.round(num).toLocaleString() + "x";

    function runFibonacciZoom(timestamp) {
        if (!fStartTime) fStartTime = timestamp;
        const elapsed = timestamp - fStartTime;
        let directionProgress;
        let isPaused = false;

        if (elapsed < fDuration) {
            directionProgress = elapsed / fDuration;
        } else if (elapsed < fDuration + fPauseTime) {
            directionProgress = 1;
            isPaused = true;
        } else if (elapsed < (fDuration * 2) + fPauseTime) {
            const timeIntoOutro = elapsed - fDuration - fPauseTime;
            directionProgress = 1 - (timeIntoOutro / fDuration);
        } else {
            stopZoom();
            return;
        }

        // Eg måtte leggje inn ein glidande overgang til målkoordinatane for å unngå
        // eit stygt hopp inn i posisjon
        const glideEase = 1 - Math.pow(1 - directionProgress, 10);
        const zoomEase = Math.pow(directionProgress, 2);
        
        const currentW = fStart.w * Math.pow((fEndWidth / fStart.w), zoomEase);
        const currentH = fStart.h * Math.pow((fEndHeight / fStart.h), zoomEase);
        const multiplier = fStart.w / currentW;

        if (isPaused) {
            zoomBtn.textContent = `${pageText.maxZoom}${formatX(multiplier)}`;
        } else {
            zoomBtn.textContent = `${pageText.zooming}${formatX(multiplier)}`;
        }

        const currentCenterX = startCenter.x + (fTarget.x - startCenter.x) * glideEase;
        const currentCenterY = startCenter.y + (fTarget.y - startCenter.y) * glideEase;
        const currentX = currentCenterX - (currentW / 2);
        const currentY = currentCenterY - (currentH / 2);

        fibSvg.setAttribute('viewBox', `${currentX} ${currentY} ${currentW} ${currentH}`);

        if (isZooming) {
            requestAnimationFrame(runFibonacciZoom);
        }
    }

    function stopZoom() {
        isZooming = false;
        fStartTime = null;
        zoomBtn.textContent = pageText.diveBackIn;
        zoomBtn.style.opacity = "1";
        zoomBtn.style.pointerEvents = "auto";
        fibSvg.setAttribute('viewBox', `${fStart.x} ${fStart.y} ${fStart.w} ${fStart.h}`);
    }

    zoomBtn.addEventListener('click', () => {
        if (isZooming) return;
        isZooming = true;
        fStartTime = null;
        zoomBtn.style.opacity = "0.9";
        zoomBtn.style.pointerEvents = "none";
        requestAnimationFrame(runFibonacciZoom);
    });
}             
                            

Så var det utfordringa med å leggje inn kodevindauge på både denne sida og TeXtract, der me måtte få fargelegginga til å lese syntaksen til både JavaScript og LaTeX. Me brukte Shiki for dette, og la til ei ekstra .json-fil for at fargelegginga skulle bli rett for expl3, då ExplSyntax-miljøet endrar den normale syntaksen til LaTeX.

Til slutt så brukte me den mest oppdaterte, minst oppblåste metoden for å gjere favikonet vårt kompatibelt med forskjellige nettlesarar. Løysinga vår var like enkel som favikonet sjølv.

Favicon
favikon.html

<head>
    ⋮ 
    <link rel="icon" href="/favicon.ico" sizes="32x32">
    <link rel="icon" href="/icon.svg" type="image/svg+xml">
    <link rel="apple-touch-icon" href="/apple-touch-icon.png">
</head> 
                

Som vanleg, tilførte Æxis vektorgrafikken, noko som tillét favikonet vårt hovudsakleg vere ein .svg. Me brukte Inkscape for å lage ein 180 × 180 piksel .png favikon for Apple, og me brukte GIMP for å lage ein .ico reserveløysing, utstyrt med både 32 × 32 og 16 × 16 versjonar. Med litt forarbeid og tre linjer kode garanterte me at favikonet vårt ville fungere i praktisk talt alle situasjonar.

Denne framgangsmåten er emblematisk av vår kjernefilosofi av minimalistisk webutvikling. Om du ynskjer enkle, elegante, lette, kjappe og semantiske nettsted, alt levert saumlaust i tandem med andre relaterte tenester, så er Føx / Kvåle Solutions det rette valet for deg.