<|start_filename|>test/animate.js<|end_filename|>
require('chai').should();
const expect = require('chai').expect;

const animate = require('../lib/animate');

describe('animate()', () => {
  it('should give a function that properly animate multiple values.', () => {
    let transform = { current: 'x', total: 't', window: 'w' };
    let frames = {
      0: { a: 0, b: 1 },
      1: { a: 1, b: 0 },
    };

    let tick = animate(frames)(transform);
    let v1 = tick({x: .75, t: 1, w: 1});
    let v2 = tick({x: .25, t: 1, w: 1});

    v1.a.should.equal(.75);
    v1.b.should.equal(.25);
    v2.a.should.equal(.25);
    v2.b.should.equal(.75);
  });

  it('should properly handle properties missing from frames in between.', () => {
    let transform = { current: 'x', total: 't', window: 'w' };
    let frames = {
      0: { a: 0, b: 1 },
      1: { a: 1 },
      2: { a: 2, b: 0 },
    };

    let tick = animate(frames)(transform);
    let v = tick({x: 1, t: 1, w: 1});

    v.a.should.equal(1);
    v.b.should.equal(.5);
  });

  it('should properly handle properties missing from frames in the start or the end.', () => {
    let transform = { current: 'x', total: 't', window: 'w' };
    let frames = {
      0: { a: 0 },
      1: { a: 1, b: 1 },
      2: { b: 0 },
    };

    let tick = animate(frames)(transform);
    let v1 = tick({x: .5, t: 1, w: 1});
    let v2 = tick({x: 1.5, t: 1, w: 1});

    v1.a.should.equal(.5);
    v1.b.should.equal(1);
    v2.a.should.equal(1);
    v2.b.should.equal(.5);
  });

  it('should take the unit of the first frame key as the unit of all and handle that properly.', () => {
    let transform = { current: 'x', total: 't', window: 'w' };
    let frames = {
      '0vw': { a: 1 },
      '100px': { a: 0 },
    };

    animate(frames)(transform)({x: 75, t: 2876, w: 100}).a.should.equal(.25);
  });

  it('should properly handle property values with units and values without.', () => {
    let transform = { current: 'x', total: 't', window: 'w' };
    let frames = {
      0: { a: '2kg', b: 0 },
      1: { a: '1kg', b: 1 }
    };

    let v = animate(frames)(transform)({x: .25, t: 1, w: 1});
    v.a.should.equal('1.75kg');
    v.b.should.equal(.25);
  });

  it('should fall back to current="current", total="total" & window="window" if no transform is given.', () => {
    let frames = {
      '0px': { a : 1, b: '100vw' },
      '128px': { a : 3, b: '50vw' }
    };

    let v = animate(frames)()({current: 64, total: 1640, window: 860 });
    v.a.should.equal(2);
    v.b.should.equal('75vw');
  });
});


<|start_filename|>index.html<|end_filename|>
<!doctype html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>

  <script src="https://unpkg.com/gleit"></script>
  <link href="https://fonts.googleapis.com/css?family=Share+Tech+Mono" rel="stylesheet">

  <link rel="stylesheet"
    href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/default.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
  <script>hljs.initHighlightingOnLoad();</script>

  <link href="styles.css" rel="stylesheet">
</head>
<body>
  <!-- === animated stuff === -->

  <!-- the orange circle -->

  <div class="circle1"
    data-gleit='{
      "0vh": {"scale": 0, "opacity": 0},
      "10vh": {"opacity": 1},
      "100vh": {"scale": 1},
      "200vh": {"scale": 1}, "300vh": {"scale": 0.4}
    }'></div>

  <script>
    window.addEventListener('load', function() {
      gleit.animate().on(gleit.verticalScroll());
    });
  </script>

  <style>
    .circle1 {
      position: fixed; left: 50vw; top: 50vh;
      margin-left: -75vmax; margin-top: -75vmax;
      width: 150vmax; height: 150vmax;
      border-radius: 150vmax;
      background: #ffa726; z-index: -1000;
    }
  </style>

  <!-- the diamonds -->

  <div class="diamond" id="d1" data-gleit='{"100vh": {"scale": 0, "rotate": "45deg"}, "160vh": {"scale": 1}, "240vh": {"scale": 1}, "300vh": {"scale": 0.3}}'></div>
  <div class="diamond" id="d2" data-gleit='{"100vh": {"scale": 0, "rotate": "45deg"}, "160vh": {"scale": 1.5}, "240vh": {"scale": 1.5}, "300vh": {"scale": 0.5}}'></div>
  <div class="diamond" id="d3" data-gleit='{"100vh": {"scale": 0, "rotate": "45deg"}, "160vh": {"scale": 0.85}, "240vh": {"scale": 0.85}, "300vh": {"scale": 0.2}}'></div>

  <script>
    window.addEventListener('load', function() {
      gleit.animate(document.getElementsByClassName('diamond'),
          { '0vw': { translateX: '10vw' }, '100vw': { translateX: '-10vw' }})
        .on(gleit.mouseMove.client.x);

      gleit.animate(document.getElementsByClassName('diamond'),
          { '0vh': { translateY: '10vh' }, '100vh': { translateY: '-10vh' }})
        .on(gleit.mouseMove.client.y);
    });
  </script>

  <style>
    .diamond {
      position: fixed; width: 10vmax; height: 10vmax;
      border: 5vmax solid #673ab7; z-index: -999; }
    .diamond#d1 { left: 10vw; top: 5vh; }
    .diamond#d2 { left: 20vw; bottom: 5vh; }
    .diamond#d3 { right: 5vw; top: 40vh; }
  </style>

  <!-- the pink bar -->

  <div id="bar"></div>

  <script>
    window.addEventListener('load', function() {
      gleit.animate(document.getElementById('bar'), {
        "10vh": { rotate: "45deg" },
        "25vh": { rotate: "60deg" },
        "30vh": { rotate: "60deg" },
        "40vh": { rotate: "90deg" },
        "60vh": { rotate: "90deg" },
        "70vh": { rotate: "120deg" },
        "75vh": { rotate: "120deg" },
        "90vh": { rotate: "135deg" }
      }).on(gleit.mouseMove.client.y);

      gleit.animate(document.getElementById('bar'), {
        "0vw": { translateX: "100vw" },
        "100vw": { translateX: "0vw" }
      }).on(gleit.mouseMove.client.x);
    });
  </script>

  <style>
    #bar {
      position: fixed; height: 50vmax; width: 200vmax;
      top: 0vh; right: 0vw; z-index: -1001;
      background: #ec407a; transform: rotate(45deg);
    }
  </style>

  <!-- === the content === -->

  <!-- the intro -->

  <div class="full c">
    <div class="text">
      <h1>GLEIT</h1>
      <h2>super easy interactive html animations.</h2>
      <p>
        with <b>GLEIT</b> you can easily do interactive animations based on page scroll, mouse movement, etc.
      </p>
      <pre><code class="html">&lt;script src="https://unpkg.com/gleit"&gt;&lt;/script&gt;</code></pre>
      <p>or</p>
      <pre><code class="bash">npm i gleit</code></pre>
      <pre><code class="html">&lt;script src="node_modules/gleit/dist/gleit.min.js"&gt;&lt;/script&gt;</code></pre>
      <p>scroll down to see examples.</p>
    </div>
  </div>

  <!-- animate on scroll section -->

  <div class="full c">
    <div class="text">
      <h2>animate based on page scroll</h2>
      <p>
        this is the code for the orange circle:<br>
        &gt; obviously run it in a page that scrolls beyond '100vh'
      </p>
      <pre><code class="html">&lt;div class="circle1"
  data-gleit='{
    "0vh": {"scale": 0, "opacity": 0},
    "10vh": {"opacity": 1},
    "100vh": {"scale": 1}
  }'&gt;&lt;/div&gt;

&lt;script&gt;
  window.addEventListener('load', function() {
    gleit.animate().on(gleit.verticalScroll());
  });
&lt;/script&gt;

&lt;style&gt;
  .circle1 {
    position: fixed; left: 50vw; top: 50vh;
    margin-left: -75vmax; margin-top: -75vmax;
    width: 150vmax; height: 150vmax;
    border-radius: 150vmax;
    background: #ffa726; z-index: -1000;
  }
&lt;/style&gt;</code></pre>
    </div>
  </div>

  <!-- animate on mouses move section -->

  <div class="full c">
    <div class="text">
      <h2>animate based on mouse position</h2>
      <p>
        this is the code for the purple diamonds:<br>
        &gt; obviously this only works on desktop
      </p>
      <pre><code class="html">&lt;div class="diamond" id="d1"&gt;&lt;/div&gt;
&lt;div class="diamond" id="d2"&gt;&lt;/div&gt;
&lt;div class="diamond" id="d3"&gt;&lt;/div&gt;

&lt;script&gt;
  window.addEventListener('load', function() {
    gleit.animate(document.getElementsByClassName('diamond'),
        { '0vw': { translateX: '10vw', rotate: '45deg' },
          '100vw': { translateX: '-10vw' }})
      .on(gleit.mouseMove.client.x);

    gleit.animate(document.getElementsByClassName('diamond'),
        { '0vh': { translateY: '10vh' },
          '100vh': { translateY: '-10vh' }})
      .on(gleit.mouseMove.client.y);
  });
&lt;/script&gt;

&lt;style&gt;
  .diamond {
    position: fixed; width: 10vmax; height: 10vmax;
    border: 5vmax solid #673ab7; z-index: -999; }
  .diamond#d1 { left: 10vw; top: 5vh; }
  .diamond#d2 { left: 20vw; bottom: 5vh; }
  .diamond#d3 { right: 5vw; top: 40vh; }
&lt;/style&gt;</code></pre>
    </div>
  </div>

  <!-- outro section -->

  <div class="full c">
    <div class="text">
      <h3>&gt; looking for more examples?</h3>
      <p>
        checkout the
        <a href="https://github.com/loreanvictor/gleit/blob/master/index.html" target="_blank">SOURCE OF THIS PAGE</a>
        , or read the
        <a href="https://github.com/loreanvictor/gleit/blob/master/README.md" target="_blank">README</a>
        for more examples and more detailed documentation.
      </p>
    </div>
  </div>

  <!-- footer -->

  <div class="footer">
    <a href="https://github.com/loreanvictor/gleit" target="_blank">GITHUB</a>
    |
    <a href="https://www.npmjs.com/package/gleit" target="_blank">NPM</a>
  </div>
</body>


<|start_filename|>lib/render.js<|end_filename|>
function render(props) {
  var styleString = '';
  var transformString = '';

  if (props.translateX !== undefined) transformString += ' translateX(' + props.translateX + ')';
  if (props.translateY !== undefined) transformString += ' translateY(' + props.translateY + ')';

  if (props.scale !== undefined) transformString += ' scale(' + props.scale + ')';
  else {
    if (props.scaleX !== undefined) transformString += ' scaleX(' + props.scaleX + ')';
    if (props.scaleY !== undefined) transformString += ' scaleY(' + props.scaleY + ')';
  }

  if (props.rotate !== undefined) transformString += ' rotate(' + props.rotate + ')';
  else {
    if (props.rotateX !== undefined) transformString += ' rotateX(' + props.rotateX + ')';
    if (props.rotateY !== undefined) transformString += ' rotateY(' + props.rotateY + ')';
    if (props.rotateZ !== undefined) transformString += ' rotateZ(' + props.rotateZ + ')';
  }

  if (props.opacity !== undefined) styleString += 'opacity: ' + props.opacity + ';';

  if (transformString.length > 0)
    styleString = 'transform:' + transformString + ';' + styleString;

  return function(element) {
    element.setAttribute('style', styleString);
  }
}

module.exports = render;


<|start_filename|>test/linear_ext.js<|end_filename|>
require('chai').should();
const expect = require('chai').expect;

const linear_ext = require('../lib/linear_ext');


describe('linear_ext', () => {
  describe('linear_ext.single()', () => {
    it('should do proper linear extrapolation between two given points.', () => {
      let ext = linear_ext.single([0, 2], [1, 1]);
      ext(0.5).should.equal(1.5);
      ext(0.75).should.equal(1.25);
    });

    it('for ([a0, a1], b) and k <= a0, it should give a1', () => {
      let ext = linear_ext.single([0, 2], [1, 1]);
      ext(0).should.equal(2);
      ext(-1).should.equal(2);
    });

    it('for (a, [b0, b1]) and k >= b0, it should give b1', () => {
      let ext = linear_ext.single([0, 2], [1, 1]);
      ext(1).should.equal(1);
      ext(2).should.equal(1);
    });

    it('for ([x, y], [x, z]) should give y for every input.', () => {
      let ext = linear_ext.single([0, 2], [0, 1]);
      ext(0).should.equal(2);
      ext(1).should.equal(2);
      ext(-1).should.equal(2);
    });

    it('should give out the value of one point if the other is not given.', () => {
      linear_ext.single([0, 2])(128).should.equal(2);
      linear_ext.single(undefined, [1, 1])(-128).should.equal(1);
    });

    it('should throw proper error if invalid input is given.', () => {
      expect(() => linear_ext.single()).to.throw();
      expect(() => linear_ext.single(2, 'hellow')).to.throw();
      expect(() => linear_ext.single([0])).to.throw();
    })
  });

  describe('linear_ext.multi()', () => {
    it('should do proper linear extrapolation for multiple points.', () => {
      let ext = linear_ext.multi([[0, 0], [1, 1], [2, 0]]);
      ext(0.5).should.equal(0.5);
      ext(1.5).should.equal(0.5);
    });

    it('should return the min(max) value for input lesser(greater) than the min(max) point.', () => {
      let ext = linear_ext.multi([[0, 0], [1, 1], [2, 0]]);
      ext(0).should.equal(0);
      ext(2).should.equal(0);
      ext(-1).should.equal(0);
      ext(3).should.equal(0);
    });
  });
});


<|start_filename|>lib/units.js<|end_filename|>
function decompose(str) {
  var split = str.split(/(\-?(?:(?:\d+(?:\.\d+)?)|(?:\.\d+)))([^(\d|\s)][^\s]*)?/);
  if (split && split.length == 4) {
    var val = split[1] * 1;
    var unit = split[2];
    if (unit) unit = unit.toLowerCase();

    return [val, unit];
  }
  else
    throw new Error('improper input: ' + str);
}

function position(ref, unit, transform) {
  if (!transform) transform = {
    current: 'current',
    total: 'total',
    window: 'window'
  };

  if (!('current' in transform)) throw new Error('transform missing "current" key: ' + transform);
  if (!('total' in transform)) throw new Error('transform missing "total" key: ' + transform);
  if (!('window' in transform)) throw new Error('transform missing "window" key: ' + transform);

  if (!(transform.current in ref)) throw new Error('ref missing "' + transform.current + '" key: ' + ref);
  if (!(transform.total in ref)) throw new Error('ref missing "' + transform.total + '" key: ' + ref);

  var _current = ref[transform.current];
  var _total = ref[transform.total];
  var _window = _total;

  if (transform.window in ref) _window = ref[transform.window];

  if (unit == 'px' || unit == '' || unit == 'ms' || !unit) return _current;
  if (unit == 's') return _current / 1000.0;
  if (unit == 'm') return _current / 60000.0;
  if (unit == '%') return _current / _total * 100;
  if (unit == 'vh' || unit == 'vw') return _current / _window * 100;
  if (unit == 'w') return _current / _window;

  throw new Error('Improper unit: ' + unit);
}

module.exports.decompose = decompose;
module.exports.position = position;


<|start_filename|>lib/ref/hscroll.js<|end_filename|>
function scroll(host) {
  var eventHost = host;
  var scrollHost = host;

  if (!host || host === document || host === document.documentElement || host === document.body || host === window) {
    eventHost = window;
    scrollHost = undefined;
  }

  return function(factory) {
    var tick = factory({
      current: 'scrollLeft',
      total: 'scrollWidth',
      window: 'clientWidth',
    });

    var dochost = {
      scrollLeft: document.body.scrollLeft || document.documentElement.scrollLeft,
      clientWidth: window.innerWidth,
      scrollWidth: document.body.scrollWidth,
    };

    eventHost.addEventListener('scroll', function() {
      var dochost = {
        scrollLeft: document.body.scrollLeft || document.documentElement.scrollLeft,
        clientWidth: window.innerWidth,
        scrollWidth: document.body.scrollWidth,
      };

      tick(scrollHost || dochost);
    });

    tick(scrollHost || dochost);
  }
}

module.exports = scroll;


<|start_filename|>styles.css<|end_filename|>
* { font-family: 'Share Tech Mono', monospace; }
html, body { padding: 0 !important; margin: 0 !important; }

a {
  color: black !important;
  text-decoration: none !important;
  outline: none !important;
  padding: 2px 4px;
  border-radius: 3px;
  transition: background .15s, color .15s;
}

a:hover {
  background: black;
  color: white !important;
}

.c {
  display: flex;
  align-items: center;
  justify-content: center;
}

.text {
  width: 512px;
  max-width: 80vw;
}

.centered { text-align: center; }
.full { min-height: 100vh; }

.footer {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  text-align: center;
  padding-bottom: 16px;
}

.hljs {
  background: #212121;
  color: #e0e0e0;
  border-radius: 3px;
  max-height: 256px;
}

.hljs-tag { color: #9e9e9e; }
.hljs-attr { color: #ffca28; }
.hljs-string { color: #9ccc65; }
.hljs-built_in,
.hljs-function { color: #26a69a; }
.hljs-selector-class { color: #448aff; }
.hljs-selector-id { color: #29b6f6; }
.hljs-attribute { color: #7986cb; }
.hljs-number { color: #9ccc65; }


<|start_filename|>gleit.js<|end_filename|>
import animate from './lib/animate';
import render from './lib/render';

import scroll from './lib/ref/scroll';
import hscroll from './lib/ref/hscroll';
import mouse from './lib/ref/mouse';
import time from './lib/ref/time';


import './polyfill/map';
import './polyfill/assign';
import './polyfill/is_array';
import './polyfill/request_animation_frame';


function gleit() {}

gleit.prototype.animateProp = animate;
gleit.prototype.renderStyles = render;

gleit.prototype.verticalScroll = scroll;
gleit.prototype.horizontalScroll = hscroll;
gleit.prototype.mouseMove = mouse;
gleit.prototype.timeline = time;

gleit.prototype.animate = function(elements, frames) {
  if (!elements) {
    if (document.querySelectorAll) {
      return this.animate(document.querySelectorAll('[data-gleit]'), frames);
    }
  }

  if (!Array.isArray(elements) &&
      !(elements.constructor && elements.constructor.name &&
        (elements.constructor.name === 'HTMLCollection' ||
        elements.constructor.name === 'NodeList'
        ))) return this.animate([elements], frames);

  var funcs = [];
  var animate = undefined;

  if (frames)
    animate = this.animateProp(frames);

  for (var index = 0; index < elements.length; index++) {
    var _animate = animate;
    var _el = elements[index];

    if (!_animate && _el.hasAttribute('data-gleit')) {
      try {
        var _frames = JSON.parse(_el.getAttribute('data-gleit'));
        _animate = this.animateProp(_frames);
      }
      catch(err) {}
    }

    if (_animate)
      funcs.push([_el, _animate]);
  }

  var _this = this;

  function tick(transform) {
    var animations = funcs.map(function(item) {
      return [item[0], item[1](transform)];
    });

    return function(ref) {
      window.requestAnimationFrame(function() {
        for (var index in animations) {
          var animation = animations[index];
          var computedStyles = animation[1](ref);
          var element = animation[0];
          var prior = element.getAttribute('data-gleit-computed');
          if (prior)
            computedStyles = Object.assign({}, JSON.parse(prior), computedStyles);
          element.setAttribute('data-gleit-computed', JSON.stringify(computedStyles));
          _this.renderStyles(computedStyles)(animation[0]);
        }
      });
    }
  }

  return {
    on: function(bind) {
      bind(tick);
    }
  }
}

export default new gleit();


<|start_filename|>lib/linear_ext.js<|end_filename|>
function single(a, b) {
  if (!a && b && b[1] !== undefined) return function(k) { return b[1]; }
  if (!b && a && a[1] !== undefined) return function(k) { return a[1]; }

  if (!a || a[0] === undefined || a[1] === undefined ||
      !b || b[0] === undefined || b[1] === undefined) throw new Error('bad input: ' + a + ' , ' + b);

  if (a[0] == b[0])
    return function(k) { return a[1]; }
  return function(k) {
    if (k <= a[0]) return a[1];
    if (k >= b[0]) return b[1];
    return (k - a[0])/(b[0] - a[0]) * (b[1] - a[1]) + a[1];
  }
}

require('../polyfill/filter');

function before(pts, k) {
  var lesser = pts.filter(function(pt) { return pt[0] <= k; });
  if (lesser.length > 0) return lesser[lesser.length - 1];
  return undefined;
}

function after(pts, k) {
  var greater = pts.filter(function(pt) { return pt[0] >= k; });
  if (greater.length > 0) return greater[0];
  return undefined;
}

function multi(pts) {
  return function(k) {
    return single(before(pts, k), after(pts, k))(k);
  }
}

module.exports.single = single;
module.exports.multi = multi;


<|start_filename|>lib/ref/time.js<|end_filename|>
var units = require('../units');

function time(duration, opts) {
  var mode = 0; // -- run once --
  var direction = 1;

  if (typeof duration === 'string') {
    var decomposed = units.decompose(duration);
    if (decomposed[1] === 'ms') duration = decomposed[0];
    else if (decomposed[1] === 's') duration = decomposed[0] * 1000;
    else if (decomposed[1] === 'm') duration = decomposed[1] * 60000;
    else throw new Error('unrecognized time unit: ' + decomposed[1]);
  }

  if (opts && opts.loop) mode = 1; // -- loop back --
  if (opts && opts.bounce) mode = 2; // -- bounce back --

  var dt = 1000.0 / 60;
  var now = 0;

  return function(factory) {
    var tick = factory();

    var _exec;
    _exec = function() {
      tick({ current: now, total: duration });
      now += direction * dt;
      if (now > duration) {
        if (mode == 0) return;
        if (mode == 1) now = 0;
        if (mode == 2) {
          direction = -1;
          now = duration;
        }
      }

      if (now < 0) {
        if (mode == 0) return;
        if (mode == 1) now = duration;
        if (mode == 2) {
          direction = 1;
          now = 0;
        }
      }

      setTimeout(_exec, dt);
    };

    _exec();
  }
}

module.exports = time;


<|start_filename|>package.json<|end_filename|>
{
  "name": "gleit",
  "version": "1.0.1",
  "description": "super easy interactive html animations.",
  "main": "gleit.js",
  "files": [
    "lib",
    "polyfill",
    "dist"
  ],
  "unpkg": "dist/gleit.min.js",
  "scripts": {
    "test": "mocha",
    "prepare": "rollup -c"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/loreanvictor/gleiten.git"
  },
  "keywords": [
    "html",
    "animation",
    "scroll",
    "interactive",
    "mouse"
  ],
  "author": "<NAME> <<EMAIL>>",
  "license": "MIT",
  "bugs": {
    "url": "https://github.com/loreanvictor/gleiten/issues"
  },
  "homepage": "https://loreanvictor.github.io/gleit/",
  "devDependencies": {
    "chai": "^4.2.0",
    "mocha": "^5.2.0",
    "rollup": "^1.0.0",
    "rollup-plugin-commonjs": "^9.2.0",
    "rollup-plugin-uglify": "^6.0.0"
  },
  "dependencies": {}
}
