Как реализовать эффекты кнопки Google бумаги
Дизайн бумаги / материалов Google http://www.google.com/design/spec/material-design/introduction.html - это действительно чистый вид, который, я думаю, будет очень полезен. У Polymer есть куча "бумажных элементов", готовых к работе, и веб-сообщество уже пытается найти разные способы его реализации. Для этого вопроса я специально смотрю на эффект нажатия кнопки.
Он имеет пульсацию цвета активации, которая излучается от вашего клика. Вот пример полимера: http://www.polymer-project.org/components/paper-elements/demo.html, вот пример css jquery: http://thecodeplayer.com/walkthrough/ripple-click-effect-google-material-design
У меня вопрос, как это осуществить?
Взгляните на пример полимера. Когда вы нарисуете, он излучает цветовой сдвиг фона, а не цветную рябь непрозрачности в другом примере. Он удерживается, когда достигает своего предела, а затем при наведении мыши быстро исчезает.
Поскольку я мог легко увидеть код, стоящий за вторым примером, я попытался реализовать его таким же образом, как и раньше, за исключением использования сенсорных событий вместо щелчка, так как я хотел, чтобы он сохранял эффект, если все, что я делал, было касанием, но не выпуском,
Я пытался масштабировать, перемещая положение, устанавливая непрозрачность, но получить расположение и эффект излучения наружу из точки касания было за пределами меня или, по крайней мере, с того времени, которое я вложил до сих пор. По правде говоря, я немного опытен в отделе анимации в целом.
Есть мысли о том, как это реализовать?
3 ответа
Я также хотел этот эффект, но я также не видел никаких реализаций. Я решил использовать CSS-радиальный градиент на фоновом изображении кнопки. Я центрирую рябь (круг градиента) в точке касания / мыши. Я расширил модуль Surface, чтобы подключиться к циклу рендеринга.
Есть два Transitionables, один для диаметра градиента и один для непрозрачности градиента. Оба из них сбрасываются после взаимодействия. Когда пользователь нажимает кнопку, Surface сохраняет смещение X и Y, а затем переводит диаметр градиента в максимальное значение. Когда пользователь отпускает кнопку, он меняет прозрачность градиента на 0.
Цикл рендеринга постоянно устанавливает фоновое изображение в радиальный градиент с кружком по смещению X и Y, и получает непрозрачность и диаметр градиента от двух Transitionables.
Я не могу сказать вам, реализовал ли я эффект пульсации, используя лучшие практики, но мне нравится результат.
var Surface = require('famous/core/Surface');
var Timer = require('famous/utilities/Timer');
var Transitionable = require('famous/transitions/Transitionable');
// Extend the button surface to tap into .render()
// Probably should include touch events
function ButtonSurface() {
Surface.apply(this, arguments);
this.gradientOpacity = new Transitionable(0.1);
this.gradientSize = new Transitionable(0);
this.offsetX = 0;
this.offsetY = 0;
this.on('mousedown', function (data) {
this.offsetX = (data.offsetX || data.layerX) + 'px';
this.offsetY = (data.offsetY || data.layerY) + 'px';
this.gradientOpacity.set(0.1);
this.gradientSize.set(0);
this.gradientSize.set(100, {
duration: 300,
curve: 'easeOut'
});
}.bind(this));
this.on('mouseup', function () {
this.gradientOpacity.set(0, {
duration: 300,
curve: 'easeOut'
});
});
this.on('mouseleave', function () {
this.gradientOpacity.set(0, {
duration: 300,
curve: 'easeOut'
});
});
}
ButtonSurface.prototype = Object.create(Surface.prototype);
ButtonSurface.prototype.constructor = ButtonSurface;
ButtonSurface.prototype.render = function () {
var gradientOpacity = this.gradientOpacity.get();
var gradientSize = this.gradientSize.get();
var fadeSize = gradientSize * 0.75;
this.setProperties({
backgroundImage: 'radial-gradient(circle at ' + this.offsetX + ' ' + this.offsetY + ', rgba(0,0,0,' + gradientOpacity + '), rgba(0,0,0,' + gradientOpacity + ') ' + gradientSize + 'px, rgba(255,255,255,' + gradientOpacity + ') ' + gradientSize + 'px)'
});
// return what Surface expects
return this.id;
};
Вы можете проверить мою скрипку здесь.
Клей Потрясающая работа, любите вашу версию, я, вероятно, немного подправлю ее и использую вместо моей.
define(function(require, exports, module) {
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Modifier = require('famous/core/Modifier');
var StateModifier = require('famous/modifiers/StateModifier');
var Transform = require('famous/core/Transform');
var View = require('famous/core/View');
var Transitionable = require('famous/transitions/Transitionable');
var ImageSurface = require("famous/surfaces/ImageSurface");
var OptionsManager = require('famous/core/OptionsManager');
var ContainerSurface = require("famous/surfaces/ContainerSurface");
var EventHandler = require('famous/core/EventHandler');
var RenderNode = require('famous/core/RenderNode');
var Draggable = require('famous/modifiers/Draggable');
var Easing = require('famous/transitions/Easing');
function PaperButton(options) {
View.apply(this, arguments);
this.options = Object.create(PaperButton.DEFAULT_OPTIONS);
this.optionsManager = new OptionsManager(this.options);
if (options) this.optionsManager.patch(options);
this.rootModifier = new StateModifier({
size:this.options.size
});
this.mainNode = this.add(this.rootModifier);
this._eventOutput = new EventHandler();
EventHandler.setOutputHandler(this, this._eventOutput);
_createControls.call(this);
this.refresh();
};
PaperButton.prototype = Object.create(View.prototype);
PaperButton.prototype.constructor = PaperButton;
PaperButton.prototype.refresh = function() {
var _inactiveBackground = 'grey';
var _activeBackground = this.options.backgroundColor + '0.8)';
this.surfaceSync.setProperties({boxShadow:_makeBoxShadow(this.options.enabled ? _droppedShadow : _noShadow)});
this.surfaceSync.setProperties({background:_setBackground(this.options.enabled ? _activeBackground: _inactiveBackground)});
};
PaperButton.prototype.getEnabled = function() {
return this.options.enabled;
};
PaperButton.prototype.setEnabled = function(enabled) {
if(enabled == this.options.enabled) { return; }
this.options.enabled = enabled;
this.refresh();
};
PaperButton.DEFAULT_OPTIONS = {
size:[269,50],//size of the button
content:'Button',//button text
backgroundColor:'rgba(68, 135, 250,',//rgba values only, cliped after the third values comma
color:'white',//text color
fontSize:'21px',
enabled: true,
};
var _width = window.innerWidth;
var _height = window.innerHeight;
var _noShadow = [0,0,0,0,0];
var _droppedShadow = [0,2,8,0,0.8];
var _liftedShadow = [0,5,15,0,0.8];
var _compareShadows = function(left, right) {
var i = left.length;
while(i>0) {
if(left[i]!=right[i--]){
return false;
}
}
return true;
};
var _boxShadow = ['', 'px ', '', 'px ', '', 'px ', '', 'px rgba(0,0,0,', '', ')'];
var _makeBoxShadow = function(data) {
_boxShadow[0] = data[0];
_boxShadow[2] = data[1];
_boxShadow[4] = data[2];
_boxShadow[6] = data[3];
_boxShadow[8] = data[4];
return _boxShadow.join('');
};
var _setBackground = function(data) {
return data;
};
var _animateShadow = function(initial, target, transition, comparer, callback) {
var _initial = initial;
var _target = target;
var _transition = transition;
var _current = initial;
var _transitionable = new Transitionable(_current);
var _handler;
var _prerender = function(goal) {
return function() {
_current = _transitionable.get();
callback(_current);
if (comparer(_current, goal)) {
//if (_current == _target || _current == _initial) {
Engine.removeListener('prerender', _handler);
}
};
};
return {
play: function() {
//
//if(!this.options.enabled) { return; }
_transitionable.halt();
_transitionable.set(_target, _transition);
_handler = _prerender(_target);
Engine.on('prerender', _handler);
},
rewind: function() {
//
//if(!this.options.enabled) { return; }
_transitionable.halt();
_transitionable.set(_initial, _transition);
_handler = _prerender(_initial);
Engine.on('prerender', _handler);
},
}
}
function _createControls() {
var self = this;
var _container = new ContainerSurface({
size:self.options.size,
properties:{
overflow:'hidden'
}
});
this.mainNode.add(_container);
var clicked = new Surface({
size:[200,200],
properties:{
background:'blue',
borderRadius:'200px',
display:'none'
}
});
clicked.mod = new StateModifier({
origin:[0.5,0.5]
});
_container.add(clicked.mod).add(clicked);
this.surfaceSync = new Surface({
size:self.options.size,
content:self.options.content,
properties:{
lineHeight:self.options.size[1] + 'px',
textAlign:'center',
fontWeight:'600',
background:self.options.backgroundColor + '0.8)',
color:self.options.color,
fontSize:self.options.fontSize,
}
});
this.mainNode.add(this.surfaceSync);
this.surfaceSync.on('touchstart', touchEffect);
this.surfaceSync.on('touchend', endTouchEffect);
clicked.mod.setTransform(
Transform.scale(-1, -1, -1),
{ duration : 0, curve: Easing.outBack }
);
var animator = _animateShadow(_droppedShadow, _liftedShadow, { duration : 500, curve: Easing.outBack }, _compareShadows, function(data) {
if(!this.options.enabled) { return; }
this.surfaceSync.setProperties({boxShadow:_makeBoxShadow(data)});
}.bind(this));
function touchEffect(e){
var temp = e.target.getBoundingClientRect();
var size = this.getSize();
var offsetY = e.changedTouches[0].pageY - (temp.bottom - (size[1] / 2));
var offsetX = e.changedTouches[0].pageX - (temp.right - (size[0] / 2));
clicked.setProperties({left:offsetX+'px',top: offsetY+'px',display:'block'});
var shadowTransitionable = new Transitionable([0,2,8,-1,0.65]);
clicked.mod.setTransform(
Transform.scale(2, 2, 2),
{ duration : 350, curve: Easing.outBack }
);
animator.play();
};
function endTouchEffect(){
clicked.mod.setTransform(
Transform.scale(-1, -1, -1),
{ duration : 300, curve: Easing.outBack }
);
clicked.setProperties({display:'none'});
animator.rewind();
};
};
module.exports = PaperButton;
});
Обновите ответ Clay Smith, чтобы удовлетворить мобильную среду.
На самом деле я использую эту ButtonSuface на Phonegap/Cordova. Работает отлично.
define(function(require, exports, module) {
var Surface = require('famous/core/Surface');
var Timer = require('famous/utilities/Timer');
var Transitionable = require('famous/transitions/Transitionable');
// Extend the button surface to tap into .render()
// Probably should include touch events
function ButtonSurface() {
Surface.apply(this, arguments);
this.gradientOpacity = new Transitionable(0);
this.gradientSize = new Transitionable(0);
this.offsetX = 0;
this.offsetY = 0;
this.on('touchstart', function (data) {
this.offsetX = (data.targetTouches[0].clientX - this._element.getBoundingClientRect().left) + 'px';
this.offsetY = (data.targetTouches[0].clientY - this._element.getBoundingClientRect().top) + 'px';
this.gradientOpacity.set(0.2);
this.gradientSize.set(0);
this.gradientSize.set(100, {
duration: 250,
curve: 'easeOut'
});
});
this.on('touchend', function (data) {
this.gradientOpacity.set(0, {
duration: 250,
curve: 'easeOut'
});
});
}
ButtonSurface.prototype = Object.create(Surface.prototype);
ButtonSurface.prototype.constructor = ButtonSurface;
ButtonSurface.prototype.render = function () {
var gradientOpacity = this.gradientOpacity.get();
var gradientSize = this.gradientSize.get();
var fadeSize = gradientSize * 0.75;
this.setProperties({
backgroundImage: 'radial-gradient(circle at ' + this.offsetX + ' ' + this.offsetY + ', rgba(0,0,0,' + gradientOpacity + '), rgba(0,0,0,' + gradientOpacity + ') ' + gradientSize + 'px, rgba(255,255,255,' + gradientOpacity + ') ' + gradientSize + 'px)'
});
// return what Surface expects
return this.id;
};
module.exports= ButtonSurface;
});