Giter Site home page Giter Site logo

rich-harris / ramjet Goto Github PK

View Code? Open in Web Editor NEW
5.5K 107.0 158.0 6.77 MB

Morph DOM elements from one state to another with smooth animations and transitions

Home Page: http://www.rich-harris.co.uk/ramjet/

License: MIT License

HTML 19.85% JavaScript 76.97% CSS 2.21% Shell 0.97%
animation transitions dom fx effects css

ramjet's Introduction

ramjet

ramjet

Installation

npm install ramjet, or download ramjet.js.

Quick start

<div id='a' style='background-color: red; font-size: 4em; padding: 1em;'>a</div>
<div id='b' style='background-color: blue; font-size: 4em; padding: 1em;'>b</div>

<script src='ramjet.js'></script>
<script>
  var element1 = document.getElementById('a'),
      element2 = document.getElementById('b');

  // to repeat, run this from the console!
  ramjet.transform( element1, element2 );
</script>

Okay, so... what does this do?

Ramjet makes it look like your DOM elements are capable of transforming into one another. It does this by cloning the elements (and all their children), transforming the second element (the one we're transforming to) so that it completely overlaps with the first, then animating the two elements together until the first element (the one we're transitioning from) has exactly the same position and dimensions as the second element originally did.

It's basically the same technique used in iOS 8 to make it appear as though each app lives inside its icon.

ios8-effect

In modern browsers, it uses CSS animations, so everything happens off the main thread. The result is buttery-smooth performance, even on mobile devices.

API

ramjet.transform( a, b[, options] )

  • a is the DOM node we're transitioning from
  • b is the DOM node we're transitioning to
  • options can include the following properties:
    • done - a function that gets called once the transition completes
    • duration - the length of the transition, in milliseconds (default 400)
    • easing - a function used to control the animation. Should take a number between 0 and 1, and return something similar (though it can return a number outside those bounds, if you're doing e.g. an elastic easing function). I highly recommend eases by Matt DesLauriers, which is a handy collection of these functions
    • easingScale - if defined it will use a different easing function for scaling. It can be used to create cartoonish effects.
    • useTimer - by default, ramjet will use CSS animations. Sometimes (when transitioning to or from SVG elements, or in very old browsers) it will fall back to timer-based animations (i.e. with requestAnimationFrame or setTimeout). If you want to always use timers, make this option true - but I don't recommend it (it's much more juddery on mobile)
    • overrideClone (advanced) - look at the section Advanced options
    • appendToBody (advanced) - look at the section Advanced options

ramjet.hide( ...nodes )

Convenience function that sets the opacity of each node to 0 (temporarily disabling any transition that might otherwise interfere).

ramjet.show( ...nodes )

Opposite of ramjet.hide.

ramjet.linear, ramjet.easeIn, ramjet.easeOut, ramjet.easeInOut

A handful of easing functions, included for convenience.

Browser support

Successfully tested in IE9+, Chrome (desktop and Android), Firefox, Safari 6+ and mobile Safari - please raise an issue if your experience differs!

Developing and testing

Once you've cloned this repo and installed all the development dependencies (npm install), you can start a development server by running npm start and navigating to localhost:4567. Any changes to the source code (in the src directory) will be immediately reflected, courtesy of gobble.

To build, do npm run build.

Reliable automated tests of a library like ramjet are all but impossible. Instead npm test will start the development server and navigate you to localhost:4567/test.html, where you can visually check that the library behaves as expected.

Advanced options

The option overrideClone (function) overrides the function used to clone nodes (the default implementation uses a simple node.cloneNode()). It takes as a parameters the current node and the depth of this node compared to the original element (it is called recursively on the node subtree). It can be useful for removing annoying attributes or children from the cloned node. For example if a node contains a video element with autoplay, this can be excluded because it may be heavy to animate and you can heard the audio of it. Examples:

// cloning only the root node
ramjet.transform( element1, element2, {
  overrideClone: function (node, depth){
    if (depth == 0){
      return node.cloneNode(); // copy only the root node
    }
  }
});

// cloning everything but the id attribute
ramjet.transform( element1, element2, {
  overrideClone: function (node, depth){
    var clone = node.cloneNode();
    clone.removeAttr('id');
  }
});

// not cloning the video element
ramjet.transform( element1, element2, {
  overrideClone: function (node, depth){
    if (node.nodeType === 1 && node.tagName === "VIDEO"){
      return;
    }
    return node.cloneNode();
  }
});

By default the cloned nodes are appended to the parent to the original node. Inheriting the positioning and css inherited rules, they can behave in an unexpected way. For this reason you can use the flag appendToBody to append these nodes to the body instead. I invite everyone to set this to true and open an issue if it doesn't work, it may become the default in one of the next releases.

License

MIT.

ramjet's People

Contributors

afc163 avatar calinou avatar crohrer avatar ddknoll avatar electerious avatar kennyt avatar mralexlau avatar prayagverma avatar rich-harris avatar sithmel avatar timothygu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ramjet's Issues

Test suite

I have no idea what a good strategy for testing a library like this looks like...

Transform fails on "Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'."

Using ramjet with the target node having a fixed, rather than inherited, offset I get the following error.

Uncaught TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
    wrapNode @ ramjet.js:89
    ramjet.transform @ ramjet.js:399

The offending line being #88.

Since the target node in my case is absolutely positioned to fill the entire screen I can simply fix it for myself using: var offsetParent = node.offsetParent || document.body;, but this is obviously not a good general solution.

Ignore certain CSS properties?

I would like to ignore opacity when using ramjet, is this possible? The reason is that i am fading in the target element when ramjet is triggered, because of this, ramjet correctly transitions the position to the correct place, but the opacity to 0. Does that make sense?

ramjet.transform between 2 CSS rules?

I'm using this lib cssobj, it's creating CSS rules from JS.

Is it possible to transform between 2 CSS Rules from JS?

// class in JS using cssobj
'.class1': {
    backgroundColor: 'red',
    fontSize: '4em',
    padding: '1em'
  }
'.class2':{
    backgroundColor: 'blue',
    fontSize: '4em',
    padding: '1em'
}
......

var newClass = ramjet.transform('.class1', '.class2')

newClass will have ramjet transform animation, apply to DOM as needed.

I'm checked the src/utils and src/interpolators, it is possible? Maybe it's need a new lib to doing this?

Thank you!

Percentage-based Border Radii

Howdy! First and foremost, thanks for building + maintaining this library! It's been an invaluable asset to my open-source lib, https://github.com/mattrothenberg/vue-overdrive.

In upgrading to 0.6.0-alpha, I noticed (what I think is) a regression around percentage-based radii. Simply put, if one of the nodes passed to the transform function has a border-radius with a percentage in it, ramjet barfs with the following error.

screen shot 2018-12-24 at 10 29 00 am

Upon closer inspection, it seems like the following line might be the culprit. https://github.com/Rich-Harris/ramjet/blob/master/src/utils/parseBorderRadius.js#L1

var borderRadiusRegex = /^(\d+)px(?: (\d+)px)?$/;

I figured you might have better context than myself as to what might have changed between versions.

I've set up a Codepen that reproduces the error.

Demo: https://streamable.com/sphja
Link: https://codepen.io/mattrothenberg/pen/MZbdNx?editors=0111

❤️

How to run demo?

Hey! I don't see any docs on how to run the demo... I tried running a local webserver and serving it, and that didn't work. Then I noticed there was a gooblefile there, so I installed gobble and tried running gobble, and that failed after installing a bunch of stuff with the following:

$ gobble
gobble: server listening on port 4567
gobble: livereload server running
gobble: 03-merge done in 84ms
%cRactive.js %c0.7.3 %cin debug mode, %cmore... color: rgb(114, 157, 52); font-weight: normal; color: rgb(85, 85, 85); font-weight: normal; color: rgb(85, 85, 85); font-weight: normal; color: rgb(82, 140, 224); font-weight: normal; text-decoration: underline;
You're running Ractive 0.7.3 in debug mode - messages will be printed to the console to help you fix problems and optimise your application.

To disable debug mode, add this line at the start of your app:
  Ractive.DEBUG = false;

To disable debug mode when your app is minified, add this snippet:
  Ractive.DEBUG = /unminified/.test(function(){/*unminified*/});

Get help and support:
  http://docs.ractivejs.org
  http://stackoverflow.com/questions/tagged/ractivejs
  http://groups.google.com/forum/#!forum/ractive-js
  http://twitter.com/ractivejs

Found a bug? Raise an issue:
  https://github.com/ractivejs/ractive/issues


%cRactive.js: %cInline partial comments are deprecated.
Use this...
  {{#partial const}} ... {{/partial}}

...instead of this:
  <!-- {{>const}} --> ... <!-- {{/const}} -->' color: rgb(114, 157, 52); color: rgb(85, 85, 85);
gobble: 04-ractive done in 226ms
gobble: 05-babel done in 2602ms
gobble: 06-esperantoBundle done in 341ms
gobble: 07-derequire done in 332ms
∙∙∙∙∙∙◦ 08-browserify running...
gobble: 08-browserify transformation failed

input:  /Users/bparks/gitrepos/ramjet_morph_dom_elements/.gobble/07
Error: Cannot find module 'ractive' from '/Users/bparks/gitrepos/ramjet_morph_dom_elements/.gobble/07-derequire/1'
    at /Users/bparks/gitrepos/ramjet_morph_dom_elements/node_modules/gobble-browserify/node_modules/browserify/node_modules/resolve/lib/async.js:46:17
    at process (/Users/bparks/gitrepos/ramjet_morph_dom_elements/node_modules/gobble-browserify/node_modules/browserify/node_modules/resolve/lib/async.js:173:43)
    at ondir (/Users/bparks/gitrepos/ramjet_morph_dom_elements/node_modules/gobble-browserify/node_modules/browserify/node_modules/resolve/lib/async.js:188:17)
    at load (/Users/bparks/gitrepos/ramjet_morph_dom_elements/node_modules/gobble-browserify/node_modules/browserify/node_modules/resolve/lib/async.js:69:43)
    at onex (/Users/b

Support Jquery Selectors

Would be convenient to say:

ramjet.transform( $('#a'), $('#b') );

instead of currently having to verbosely convert jquery elements to their corresponding dom elements:

ramjet.transform( $('#a').get(0), $('#b').get(0) );

Firefox SVG problems

TypeError: Argument 1 of Window.getComputedStyle is not an object.

Something to do with SVG elements not having an offset parent...

Rotate

Any chance this will work with certain CSS transformations like rotate, i.e.:

#a{
  ...
}

#b{
  ...
  transform: rotate(90deg);
}

Also, rotating twice from beginning to end on something like

#b{
  ...
  transform: rotate(720deg);
}

would be super cool!

Web Animations API

Hi @Rich-Harris,

Would it be possible to create a ramjet that can rely entirely on the web animations api? It would be great to be able to make ramjets of different elements and composite them into one big animation, e.g:

var anim = new AnimationSequence([
  ramjet.transform(document.querySelector('button.nav.active'), document.querySelector('header.page')),
  new Animation(document.querySelector('img.avatar'), [ { transform: 'translateX(300px) rotate(30deg) translateX(-400px) } ])
], { duration: 500, fill: 'forwards' });

var player = document.timeline.play(anim);
player.play();

The Web Animations API polyfill is used by Polymer for example and works really well, providing cross-browser support.
https://github.com/web-animations/web-animations-js

scrollTop is not cloned

If you're transitioning from/to an element with overflow:auto/scroll, and which the user has already scrolled before ramjetting, this causes an ugly jump at the beginning/end of the transition, breaking the illusion.

It's also possible that it's not the outer element but some descendent that is scrollable... So to fix this properly you'd need to walk through all descendent elements and copy over the scrollTop for each one (if it's not 0). Not sure how feasible that is.

ui-view / angular-ui-router

[feature request]

Would be awesome to have an angular directive which incorporates ramjet into angular-ui-router's ui-view s.t. state transitions could optionally include morphing.

iPhone glitch: "toNode" disappears for about 0.5 seconds

I'm getting this bug in my own code but you can also see it happening in the multicolour-number-grid demo (at http://www.rich-harris.co.uk/ramjet/ under More Examples).

To reproduce: Visit that page on an iPhone 5 (and maybe other devices, I haven't tested). Tap one of the items. It animates smoothly, but then when it's finished, and the big photo is at its full size, it waits a moment then the big photo disappears and reappears. It's gone for about 0.5 seconds (it's very noticeable).

I'm guessing the tempoary disappearance is due to Ramjet removing the toNode clone before firing the 'done' callback that unhides the real toNode. But it's weird that there would be such a big gap of time; maybe there's something else going on?

Images gets transparent on transition start

Hi,

I'm using vue-overdrive, but under the hood it uses your ramjet. So maybe you can help me ;)

I have done a screencast to demonstrate the problem. As you can see in the video, if I click an image it fades everything out and then beginns to fade in the image again. I have done also a small sandbox example

In vue-overdrive the ramjet magic happens there

  ramjet.hide(clone)
  ramjet.hide(this.$el.firstChild)
  ramjet.transform(clone, this.$el.firstChild, opts)

I have allready tried out to set the opacity manually

this.$el.firstChild.style.opacity = 1
clone.opacity = 1

This is the whole file

I've seen that on the elements the style opacity: 1 is set, but I do not put this by myself, maybe it has something to do with that behavior?
image

I have updated ramjet from 0.5.0 to 0.6.0-alpha, because I have seen that there are some updates about opacity.

Do you think that's more a vuejs/vue-overdrive problem or more related to ramjet?

Artifacts in Firefor for Android

In Firefox for Android I every now an then get some pretty horrible artifact in the demo at the top of the Ramjet homepage.

The best description I can give is that the previous elements appears to do some kind of ghosting were it's not hidden properly.

Elements are semi-transparent when animating

At the middle of the animation, both elements are at 50% opacity, meaning that combined together, they are 75% opacity (not 100% like they should)

The bottom element should always be 100% opacity, only the top element should change.

screenshot:
screenshot 2015-05-01 09 52 02

Great library btw, can't wait to use it in a project.

Two steps transition ?

Hi,

I wonder if it could be possible to morph two elements in two steps instead of one.

...Explanation :

I'm making a transition between two pages. Lets say the first one is a listing of blog articles and the other is the blog article itself. I want the smaller version of an article (in the listing) to transition into the full version of the article (in the single article page). But because the second one is on a different page, the layouts differs. I can only have either the smaller article OR the full article in the DOM at the same time. Therefore I can't transition between the two elements because the state of the elements are recorded at the same time.

What I would need is :

  • Step 1 : record the state of the DOM element A in the listing page
  • Step 2 : erase the listing page layout and replace it with the single article page
  • Step 3 : morph my recorded state of the DOM element A into the DOM element B (the full article)

Achieving this would be incredibly awesome to me, is it hard to implement ? :)

done function - shouldn't it pass a,b as arguments?

Hey! I was wondering if the done function should pass the a,b values that transform receives as arguments?

e.g.

options.done = function(a,b)
{
  console.log(a,b);
}

Sure this could be bound using .bind, but why the extra work if it can be passed in? Thanks!

Docs need improvement [suggestion]

Small rant sorry...

You should mention somewhere that elements have to be hidden first then do ramjet.transform(), and use { done: => { ramjet.show(target) }}... and also maybe that it doesnt like jquery elements.

If the docs do say that, my apologies, I couldn't find it

Reason is all your demos use hidden elements, but its not clear how to interact with a hidden element, and the effect doesnt work at all with hidden elements..

I originally tried to setup a fiddle with jquery and ramjet and it didnt work at all, then I tried to use a hidden element as a target transition and it didnt work, so I tried all the variations of methods to hide an element, display:none, visibility:hidden, jquery hide, etc.

anyway after 30 minutes i got this: https://jsfiddle.net/davwcwmr/ cool!

Morph border radius

When manually editing CSS styles on example page, border-radius property is not morphed

capture d ecran 2015-05-04 a 18 33 09
screenshot

IE10/11 flicker

Sigh... I thought that IE11 at least might not be terrible. FFS. There's a noticeable flicker at the end of each transition - somehow it's getting the timing all messed up, and the callback is executing at the wrong moment. It's almost as if the people who make IE are rubbish at their jobs or something.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.