如何实现谷歌纸按钮效果

时间:2014-07-25 00:30:58

标签: famo.us material-design

Google的论文/材料设计http://www.google.com/design/spec/material-design/introduction.html 是一个非常干净的外观,我认为会看到很多用途。 Polymer已经准备好了一堆“纸质元素”,网络社区已经开始采用不同的方式来实现它。对于这个问题,我特别关注按钮点击效果。

它有一个激活颜色的波纹,从你的咔哒声中散发出来。这是聚合物的例子:http://www.polymer-project.org/components/paper-elements/demo.html#paper-button,这里是一个css jquery示例:http://thecodeplayer.com/walkthrough/ripple-click-effect-google-material-design

我的问题是如何实施它?

看一下聚合物示例当你mousedown时,它可能会辐射背景颜色,而不是另一个例子中的彩色不透明度波纹。它在达到极限时保持不变,然后在mouseup上它会很快淡出。

由于我可以很容易地看到第二个示例背后的代码,我尝试以类似的方式实现它,但除了使用触摸事件而不是点击,因为我希望它能保持效果,如果我所做的只是触摸但不释放。

我尝试缩放,转换位置设置不透明度,但是从触摸点向外放置和向外辐射的效果超出了我或者至少从我到目前为止投入的时间。事实上,我只是在动画部门经验丰富。

有关如何实施它的任何想法?

3 个答案:

答案 0 :(得分:4)

我也想要这种效果,但我也没见过任何实现。我决定在按钮的背景图像中使用CSS径向渐变。我将触摸/鼠标点的波纹(渐变的圆圈)居中。我扩展了Surface模块,以便挂钩渲染周期。

有两个可转换的,一个用于渐变的直径,另一个用于渐变不透明度。这两个都在交互后重置。当用户单击按钮时,Surface会存储X和Y偏移,然后将渐变直径转换为其最大值。当用户释放按钮时,它会将渐变不透明度转换为0。

渲染周期不断地将背景图像设置为径向渐变,圆圈位于X和Y偏移处,并从两个可转换曲面获得不透明度和渐变直径。

我不能告诉你我是否使用最佳实践实现了波纹按钮效果,但我喜欢结果。

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;
};

您可以查看my fiddle here

答案 1 :(得分:1)

Clay很棒的作品很喜欢你的版本我可能会稍微调整一下并使用它而不是我自己的版本。

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;
});

答案 2 :(得分:0)

更新Clay Smith以满足移动环境的答案。

实际上我在Phonegap / Cordova上使用这个ButtonSuface。效果很好。

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;
});
相关问题