Как реализовать эффекты кнопки 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;
});
Другие вопросы по тегам