Easing formula tester 📈

Set preset to 0 to write your own formula (edit the `customEasing` function)

Based on projects.stroep.nl/ease/

Log in to post a comment.

/*
Test easing equation function here
*/
const customEasing = t => 1 - Math.pow(1 - t, 5);

const presets = [
    {name: "linear", fn: t => t},
    {name: "smoothstep", fn: t => t * t * (3.0 - 2.0 * t)},
    {name: "quad in", fn: t => t * t},
    {name: "quad out", fn: t => t * (2.0 - t)},
    {name: "quad in/out", fn: t => t <= 0.5 ? t * t * 2.0 : 1.0 - (--t) * t * 2.0},
    {name: "quad out/in", fn: t => (t < 0.5) ? -0.5 * (t = (t * 2.0)) * (t - 2.0) : 0.5 * (t = (t * 2.0 - 1.0)) * t + 0.5},
    {name: "pow in", fn: t => Math.pow(t, 5)},
    {name: "pow out", fn: t => 1 - Math.pow(1 - t, 5)},
    {name: "sine in", fn: t => 1 - Math.cos((Math.PI * 0.5) * t)},
    {name: "sine out", fn: t => Math.sin(Math.PI * 0.5 * t)},
    {name: "sine in/out", fn: t => .5 - Math.cos(Math.PI * t) / 2.0},
    {name: "sine out/in", fn: t => { if (t == 0.0) return 0.0
     else if (t == 1.0) return 1.0
     else return (t < 0.5) ? 0.5 * Math.sin((t * 2.0) * Math.PI*0.5) : -0.5 * Math.cos((t * 2.0 - 1.0) * Math.PI*0.5) + 1.0
     }},
    {name: "circ in", fn: t => 1.0 - Math.sqrt(1.0 - t * t)},
    {name: "circ out", fn: t => { --t; return Math.sqrt(1.0 - t * t) }},
    {name: "circ in/out", fn: t => t <= 0.5 ? (Math.sqrt(1.0 - t * t * 4.0) - 1.0) / -2.0 : (Math.sqrt(1.0 - (t * 2.0 - 2.0) * (t * 2.0 - 2.0)) + 1.0) / 2.0},
    {name: "circ out/in", fn: t => (t < 0.5) ? 0.5 * Math.sqrt(1.0 - (t = t * 2.0 - 1.0) * t) : -0.5 * ((Math.sqrt(1.0 - (t = t * 2.0 - 1.0) * t) - 1.0) - 1.0)},
    
    {name: "cube in", fn: t => t * t * t},
    {name: "cube out", fn: t => 1.0 + (--t) * t * t},
    {name: "cube in/out", fn: t => t <= 0.5 ? t * t * t * 4.0 : 1.0 + (--t) * t * t * 4.0},
    {name: "cube out/in", fn: t =>  0.5 * ((t = t * 2.0 - 1.0) * t * t + 1.0)},
    
    {name: "quart in", fn: t => t * t * t * t},
    {name: "quart out", fn: t => 1.0 - (--t) * t * t * t},
    {name: "quart in/out", fn: t => t <= 0.5 ? t * t * t * t * 8.0 : (1.0 - (t = t * 2.0 - 2.0) * t * t * t) * 0.5 + 0.5},
    {name: "quart out/in", fn: t => (t < 0.5) ? -0.5 * (t = t * 2.0 - 1.0) * t * t * t + 0.5 : 0.5 * (t = t * 2.0 - 1.0) * t * t * t + 0.5},
    
    {name: "quint in", fn: t => t * t * t * t * t},
    {name: "quint out", fn: t => (t = t - 1) * t * t * t * t + 1.0},
    {name: "quint in/out", fn: t => ((t *= 2.0) < 1.0) ? (t * t * t * t * t) / 2.0 : ((t -= 2.0) * t * t * t * t + 2.0) / 2.0},
    {name: "quint out/in", fn: t => 0.5 * ((t = t * 2.0 - 1.0) * t * t * t * t + 1.0)},
    
    {name: "expo in", fn: t => Math.pow(2, 10.0 * (t - 1.0))},
    {name: "expo out", fn: t => -Math.pow(2, -10.0 * t) + 1.0},
    {name: "expo in/out", fn: t => t < .5 ? Math.pow(2, 10.0 * (t * 2.0 - 1.0)) / 2.0 : (-Math.pow(2, -10.0 * (t * 2.0 - 1.0)) + 2.0) / 2.0},
    {name: "expo out/in", fn: t => (t < 0.5) ? 0.5 * (1.0 - Math.pow(2, -20.0 * t)) : (t == 0.5) ?  0.5 :  0.5 * (Math.pow(2, 20.0 * (t - 1.0)) + 1.0)},
    
    {name: "back in", fn: t => t * t * (5.0 * t - 4.0)},
    {name: "back out", fn: t => 1 - --t * t * (-5.0 * t - 4.0)},
    {name: "back in/out", fn: t => { t *= 2.0
     if (t < 1.0) return t * t * (5.0 * t - 4.0) / 2.0
     t -= 2.0
     return (1 - t * t * (-5.0 * t - 4.0)) / 2.0 + 0.5
    }},
    
    {name: "elastic in", fn: t => {const ELASTIC_AMPLITUDE=1, ELASTIC_PERIOD=0.4; return -(ELASTIC_AMPLITUDE * Math.pow(2, 10 * (t -= 1)) * Math.sin((t - (ELASTIC_PERIOD / (Math.PI / 2) * Math.asin(1 / ELASTIC_AMPLITUDE))) * (Math.PI * 2) / ELASTIC_PERIOD))}},
    {name: "elastic out", fn: t => {const ELASTIC_AMPLITUDE=1, ELASTIC_PERIOD=0.4; return  (ELASTIC_AMPLITUDE * Math.pow(2, -10 * t) * Math.sin((t - (ELASTIC_PERIOD / (Math.PI * 2) * Math.asin(1 / ELASTIC_AMPLITUDE))) * (Math.PI * 2) / ELASTIC_PERIOD) +1)}},
    {name: "elastic in/out", fn: t => {const ELASTIC_AMPLITUDE=1, ELASTIC_PERIOD=0.4; if (t < 0.5) return -0.5 * (Math.pow(2, 10.0 * (t -= 0.5)) * Math.sin((t - (ELASTIC_PERIOD / 4.0)) * (Math.PI * 2) / ELASTIC_PERIOD));
     return Math.pow(2, -10 * (t -= 0.5)) * Math.sin((t - (ELASTIC_PERIOD / 4)) * (Math.PI * 2.0) / ELASTIC_PERIOD) * 0.5 + 1.0;}},
    
    {name: "steps", fn: t => Math.floor(t * 10.0) / 10.0},
    {name: "array", fn: t => [0.0, 0.4, 0.7, 0.2, 1.0][Math.floor(t * 5.0)]},
    {name: "randomish", fn: t => t + -0.03 + Math.random() * 0.06}
];

const preset = 0; // min=0, max=42, step=1
const fn = preset != 0 ? presets[preset - 1].fn : customEasing;

const sampleAmount = 100;
const turtle = new Turtle();
const text = new Text();
const size = { w: 150, h: 150 }

function walk(i) {
    const r = i / sampleAmount;
    const value = fn(r);
    const x = -size.w/2 + r * size.w;
    const y = -size.h/2 + (1 - value) * size.h;
    
    if (i == 0) {
        ui();
        turtle.jump(x, y);
    } else {
        turtle.goto(x, y);
    }
    
    if (i % 2 == 0) {
        const r = 1;
        
        turtle.jump(x, y - r);
        turtle.circle(r);
        turtle.jump(x, y);
    }
    
    return i < sampleAmount;
}

function ui() {
    // title
    turtle.jump(-size.w/2, -size.h/2 - 10);
    text.print(turtle, preset != 0 ? `Preset: ${presets[preset - 1].name}` : `Custom easing`, .32);
    
    turtle.jump(-size.w/2, size.h/2 + 10);
    
    let code = `${fn}`;
    code.split("\n").join(" ");
    let codeStrings = [];
    const maxLineLength = 72;
    while (code.length > maxLineLength) {
        codeStrings.push(code.substr(0,maxLineLength));
        code = code.substring(maxLineLength);
    }
    codeStrings.push(code.substr(0,maxLineLength));
    code = code.substring(maxLineLength);
    text.print(turtle, `${codeStrings.join("\n  ")}`, .12);
    
    
    // grid
    const totalDots = 100;
    for(let yy=0;yy<=totalDots;yy++) {
        if (yy%10==0) {
            turtle.jump(size.w/2 + 2, -size.h/2 + yy/totalDots * size.h);
            text.print(turtle, `${(1-yy/sampleAmount).toFixed(2)}`, .1);
        }
        for(let xx=0;xx<=totalDots;xx++) {
            // grid labels
            if (yy%10==0 && xx%10==0) {
                turtle.jump(-size.w/2 + xx/totalDots * size.w, size.h/2 + 3);
                text.print(turtle, `${(xx/sampleAmount).toFixed(2)}`, .1);
            }
            
            // skip inner dots
            const r = xx%10==0 || yy%10==0 ? 0.2 : 0.1;
            if (!(xx%10==0 || yy%10==0)) {
               if (xx%2==1 || yy%2==1) {
                   continue;
               }
            }
            
            // grid
            turtle.jump(-size.w/2 + xx/totalDots * size.w, -size.h/2 + yy/totalDots * size.h - r);
            turtle.circle(r);
        }
    }
}



////////////////////////////////////////////////////////////////
// Text utility code. Created by Reinder Nijhoff 2019
// https://turtletoy.net/turtle/1713ddbe99
////////////////////////////////////////////////////////////////

function Text() {
    class Text {
        print (t, str, scale) {
            t.radians();
            let pos = [t.x(), t.y()], h = t.h(), o = pos;
            str.split('').map(c => {
                const i = c.charCodeAt(0) - 32;
                if (i < 0 ) {
                    pos = o = this.rotAdd([0, 48*scale], o, h);
                } else if (i > 96 ) {
                    pos = this.rotAdd([16*scale, 0], o, h);
                } else {
                    const d = dat[i], lt = d[0]*scale, rt = d[1]*scale, paths = d[2];
                    paths.map( p => {
                        t.up();
                    	p.map( s=> {
                        	t.goto(this.rotAdd([s[0]*scale - lt, s[1]*scale], pos, h));
                        	t.down();
                        });
                    });
                    pos = this.rotAdd([rt - lt, 0], pos, h);
                }
            });
        }
        rotAdd (a, b, h) {
            return [Math.cos(h)*a[0] - Math.sin(h)*a[1] + b[0], 
                    Math.cos(h)*a[1] + Math.sin(h)*a[0] + b[1]];
        }
    }
    
const dat = ('br>eoj^jl<jqirjskrjq>brf^fe<n^ne>`ukZdz<qZjz<dgrg<cmqm>`thZhw<lZlw<qao_l^h^e_caccdeefg'+
'gmiojpkqmqporlshsercp>^vs^as<f^h`hbgdeeceacaab_d^f^h_k`n`q_s^<olmmlolqnspsrrspsnqlol>]wtgtfsereqfph'+
'nmlpjrhsdsbraq`o`makbjifjekckaj_h^f_eaecffhimporqssstrtq>eoj`i_j^k_kajcid>cqnZl\\j_hcghglhqjulxnz>c'+
'qfZh\\j_lcmhmllqjuhxfz>brjdjp<egom<ogem>]wjajs<ajsj>fnkojpiojnkokqis>]wajsj>fnjniojpkojn>_usZaz>`ti'+
'^f_dbcgcjdofrisksnrpoqjqgpbn_k^i^>`tfbhak^ks>`tdcdbe`f_h^l^n_o`pbpdofmicsqs>`te^p^jfmfogphqkqmppnrk'+
'shserdqco>`tm^clrl<m^ms>`to^e^dgefhekenfphqkqmppnrkshserdqco>`tpao_l^j^g_ebdgdlepgrjsksnrppqmqlping'+
'kfjfggeidl>`tq^gs<c^q^>`th^e_dadceegfkgnhpjqlqopqorlshserdqcocldjfhigmfoepcpao_l^h^>`tpeohmjjkikfjd'+
'hcecddaf_i^j^m_oapepjoomrjshserdp>fnjgihjikhjg<jniojpkojn>fnjgihjikhjg<kojpiojnkokqis>^vrabjrs>]wag'+
'sg<amsm>^vbarjbs>asdcdbe`f_h^l^n_o`pbpdofngjijl<jqirjskrjq>]xofndlcicgdfeehekfmhnknmmnk<icgefhfkgmh'+
'n<ocnknmpnrntluiugtdsbq`o_l^i^f_d`bbad`g`jambodqfrislsorqqrp<pcokompn>asj^bs<j^rs<elol>_tc^cs<c^l^o'+
'_p`qbqdpfoglh<chlhoipjqlqopqorlscs>`urcqao_m^i^g_eadccfckdnepgrismsorqprn>_tc^cs<c^j^m_oapcqfqkpnop'+
'mrjscs>`sd^ds<d^q^<dhlh<dsqs>`rd^ds<d^q^<dhlh>`urcqao_m^i^g_eadccfckdnepgrismsorqprnrk<mkrk>_uc^cs<'+
'q^qs<chqh>fnj^js>brn^nnmqlrjshsfreqdndl>_tc^cs<q^cl<hgqs>`qd^ds<dsps>^vb^bs<b^js<r^js<r^rs>_uc^cs<c'+
'^qs<q^qs>_uh^f_daccbfbkcndpfrhslsnrppqnrkrfqcpan_l^h^>_tc^cs<c^l^o_p`qbqepgohlici>_uh^f_daccbfbkcnd'+
'pfrhslsnrppqnrkrfqcpan_l^h^<koqu>_tc^cs<c^l^o_p`qbqdpfoglhch<jhqs>`tqao_l^h^e_caccdeefggmiojpkqmqpo'+
'rlshsercp>brj^js<c^q^>_uc^cmdpfrisksnrppqmq^>asb^js<r^js>^v`^es<j^es<j^os<t^os>`tc^qs<q^cs>asb^jhjs'+
'<r^jh>`tq^cs<c^q^<csqs>cqgZgz<hZhz<gZnZ<gznz>cqc^qv>cqlZlz<mZmz<fZmZ<fzmz>brj\\bj<j\\rj>asazsz>fnkc'+
'ieigjhkgjfig>atpeps<phnfleiegfehdkdmepgrislsnrpp>`sd^ds<dhffhekemfohpkpmopmrkshsfrdp>asphnfleiegfeh'+
'dkdmepgrislsnrpp>atp^ps<phnfleiegfehdkdmepgrislsnrpp>asdkpkpiognfleiegfehdkdmepgrislsnrpp>eqo^m^k_j'+
'bjs<gene>atpepuoxnylzizgy<phnfleiegfehdkdmepgrislsnrpp>ate^es<eihfjemeofpips>fni^j_k^j]i^<jejs>eoj^'+
'k_l^k]j^<kekvjyhzfz>are^es<oeeo<ikps>fnj^js>[y_e_s<_ibfdegeifjijs<jimfoeretfuius>ateees<eihfjemeofp'+
'ips>atiegfehdkdmepgrislsnrppqmqkphnfleie>`sdedz<dhffhekemfohpkpmopmrkshsfrdp>atpepz<phnfleiegfehdkd'+
'mepgrislsnrpp>cpgegs<gkhhjfleoe>bsphofleieffehfjhkmlompopporlsisfrep>eqj^jokrmsos<gene>ateeeofrhsks'+
'mrpo<peps>brdejs<pejs>_ubefs<jefs<jens<rens>bseeps<pees>brdejs<pejshwfydzcz>bspees<eepe<esps>cqlZj['+
'i\\h^h`ibjckekgii<j[i]i_jakbldlfkhgjkllnlpkrjsiuiwjy<ikkmkojqirhthvixjylz>fnjZjz>cqhZj[k\\l^l`kbjci'+
'eigki<j[k]k_jaibhdhfihmjilhnhpirjskukwjy<kkimiojqkrltlvkxjyhz>^vamakbhdgfghhlknlplrksi<akbidhfhhill'+
'nmpmrlsisg>brb^bscsc^d^dsese^f^fsgsg^h^hsisi^j^jsksk^l^lsmsm^n^nsoso^p^psqsq^r^rs').split('>').map(
    r=> { return [r.charCodeAt(0)-106,r.charCodeAt(1)-106, r.substr(2).split('<').map(a => {const ret 
    = []; for (let i=0; i<a.length; i+=2) {ret.push(a.substr(i, 2).split('').map(b => b.charCodeAt(0)
    -106));} return ret; })]; });

    return new Text();
}