JasonK JasonK - 1 month ago 17
CSS Question

Chrome weird (scaleY) transition behaviour

So I've been working on a lightweight plugin built on CSS transitions. It's capable of adding inline styles (transitions) on the fly.

Some parts of the plugin code (like prefixing CSS attributes) are removed to make things clearer:



(function($, window, document) {
'use strict';

var plugin = 'transition';

// Constructors
function Transition(element, animation, options) {
this.element = element;
this.animation = animation;
this.direction = null;
this.settings = $.extend({}, $.fn[plugin].defaults, options);
this.init();
}

// Instance
$.extend(Transition.prototype, {

init: function() {
var instance = this;

instance.direction = $(instance.element).is(':visible') ? // toggle
'outward':
'inward' ;

setTimeout(function() { // separate queue entry to make sure previous re-draw events are finished
instance.settings.animations.hasOwnProperty(instance.animation) ?
instance.start():
console.error('Trying to call an undefined animation');
}, 0);
},

/**
* Start the transition.
*/
start: function() {
var instance = this,
$element = $(instance.element);

// Bind handlers
$element
.one('transitionstart', function() {
instance.settings.onStart.call($element);
})
.one('transitionend', function() {
instance.end();
});

// Add inline styles
$element
.css(instance.style('start'))
.show() // ensure the element is visible
.css(instance.style('end'));
},


/**
* End the transition.
*/
end: function() {
var instance = this,
$element = $(instance.element);

instance.direction == 'inward' ?
$element.show():
$element.hide();

instance.settings.onEnd.call($element);

$element.css({
opacity: '',
transform: '',
transformOrigin: '',
transition: ''
}).dequeue();
},

/**
* Get the inline style for the transition.
*
* @param state
*/
style: function(state) {
var instance = this,
animation = instance.settings.animations[instance.animation],
direction = instance.direction,
css = {};

if (state === 'start') {
css = (direction == 'inward') ?
animation.start:
animation.end; // reversed

css['transition'] = 'all ' +
instance.settings.duration + 'ms ' +
instance.settings.curve + ' ' +
instance.settings.delay + 'ms';
} else {
css = (direction == 'inward') ?
animation.end:
animation.start; // reversed
}

return css;
}
});

// Plugin definition
$.fn[plugin] = function(animation, options) {
return this.each(function() {
$(this).queue(function() {
new Transition(this, animation, options);
});
});
};

// Default settings
$.fn[plugin].defaults = {
duration : 500,
delay : 0,
curve : 'ease',
onStart : function() {},
onEnd : function() {}
};

$.fn[plugin].defaults.animations = {
fade: {
start : { 'opacity': 0 },
end : { 'opacity': 1 }
},
scale: {
start : { 'opacity': 0, 'transform': 'scale(0.8)' },
end : { 'opacity': 1, 'transform': 'scale(1.0)' }
},
slide: {
start : { 'opacity': 0, 'transform': 'scaleY(0)', 'transform-origin': 'bottom'},
end : { 'opacity': 1, 'transform': 'scaleY(1)', 'transform-origin': 'bottom'}
}
};

})(jQuery, window, document);

$('#fading').transition('fade', {duration: 1000, delay: 1000});
$('#scaling').transition('scale', {duration: 1000, delay: 1000});
$('#sliding').transition('slide', {duration: 1000, delay: 1000});

div {
display: inline-block;
margin-bottom: 1em;
padding: 3em 2em;
background-color: #EEE;
border: 1px solid red;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="fading">Fading block</div>
<div id="scaling">Scaling block</div>
<div id="sliding">Sliding block</div>





For some reason the sliding animation doesn't work on Chrome, the element just fades in/out. FireFox and Edge don't have this issue.

The CSS being applied for the sliding animation:

slide: {
start : { 'opacity': 0, 'transform': 'scaleY(0)', 'transform-origin': 'bottom'},
end : { 'opacity': 1, 'transform': 'scaleY(1)', 'transform-origin': 'bottom'}
}


Hoping any magician could figure out what is going on.

Answer

It's a known bug with transitioning scale(0). Just change it to scale(0.01).

(function($, window, document) {
  'use strict';

  var plugin = 'transition';

  // Constructors
  function Transition(element, animation, options) {
    this.element = element;
    this.animation = animation;
    this.direction = null;
    this.settings = $.extend({}, $.fn[plugin].defaults, options);
    this.init();
  }

  // Instance
  $.extend(Transition.prototype, {

    init: function() {
      var instance = this;

      instance.direction = $(instance.element).is(':visible') ? // toggle
        'outward' :
        'inward';

      setTimeout(function() { // separate queue entry to make sure previous re-draw events are finished
        instance.settings.animations.hasOwnProperty(instance.animation) ?
          instance.start() :
          console.error('Trying to call an undefined animation');
      }, 0);
    },

    /**
     * Start the transition.
     */
    start: function() {
      var instance = this,
        $element = $(instance.element);

      // Bind handlers
      $element
        .one('transitionstart', function() {
          instance.settings.onStart.call($element);
        })
        .one('transitionend', function() {
          instance.end();
        });

      // Add inline styles
      $element
        .css(instance.style('start'))
        .show() // ensure the element is visible
        .css(instance.style('end'));
    },


    /**
     * End the transition.
     */
    end: function() {
      var instance = this,
        $element = $(instance.element);

      instance.direction == 'inward' ?
        $element.show() :
        $element.hide();

      instance.settings.onEnd.call($element);

      $element.css({
        opacity: '',
        transform: '',
        transformOrigin: '',
        transition: ''
      }).dequeue();
    },

    /**
     * Get the inline style for the transition.
     *
     * @param state
     */
    style: function(state) {
      var instance = this,
        animation = instance.settings.animations[instance.animation],
        direction = instance.direction,
        css = {};

      if (state === 'start') {
        css = (direction == 'inward') ?
          animation.start :
          animation.end; // reversed

        css['transition'] = 'all ' +
          instance.settings.duration + 'ms ' +
          instance.settings.curve + ' ' +
          instance.settings.delay + 'ms';
      } else {
        css = (direction == 'inward') ?
          animation.end :
          animation.start; // reversed
      }

      return css;
    }
  });

  // Plugin definition
  $.fn[plugin] = function(animation, options) {
    return this.each(function() {
      $(this).queue(function() {
        new Transition(this, animation, options);
      });
    });
  };

  // Default settings
  $.fn[plugin].defaults = {
    duration: 500,
    delay: 0,
    curve: 'ease',
    onStart: function() {},
    onEnd: function() {}
  };

  $.fn[plugin].defaults.animations = {
    fade: {
      start: {
        'opacity': 0
      },
      end: {
        'opacity': 1
      }
    },
    scale: {
      start: {
        'opacity': 0,
        'transform': 'scale(0.8)'
      },
      end: {
        'opacity': 1,
        'transform': 'scale(1.0)'
      }
    },
    slide: {
      start: {
        'opacity': 0.5,
        'transform': 'scaleY(0.01)',
        'transform-origin': 'bottom'
      },
      end: {
        'opacity': 1,
        'transform': 'scaleY(1)',
        'transform-origin': 'bottom'
      }
    }
  };

})(jQuery, window, document);

$('#fading').transition('fade', {
  duration: 1000,
  delay: 1000
});
$('#scaling').transition('scale', {
  duration: 1000,
  delay: 1000
});
$('#sliding').transition('slide', {
  duration: 1000,
  delay: 1000
});
div {
  display: inline-block;
  margin-bottom: 1em;
  padding: 3em 2em;
  background-color: #EEE;
  border: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="fading">Fading block</div>
<div id="scaling">Scaling block</div>
<div id="sliding">Sliding block</div>