如何实现google paper按钮效果

Posted

技术标签:

【中文标题】如何实现google paper按钮效果【英文标题】:How to implement google paper button effects 【发布时间】:2014-09-16 17:53:02 【问题描述】:

谷歌的论文/材料设计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

我的问题是如何去实现它?

看一下聚合物示例 当您按下鼠标时,它可能会发出背景颜色偏移,而不是另一个示例中的彩色不透明度波纹。它在达到极限时保持不变,然后在 mouseup 时迅速消失。

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

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

关于如何实现它的任何想法?

【问题讨论】:

【参考方案1】:

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

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

渲染周期不断地将背景图像设置为径向渐变,圆在 X 和 Y 偏移处,并从两个 Transitionable 中获取不透明度和渐变直径。

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

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。

【讨论】:

不错。我有家庭和工作的事情,而且一直不擅长跟上 SO。我之前让它工作了一点,但我想清理代码并使其更具可定制性。我会在之后发布我所拥有的。偏移对我来说很痛苦,因为我没有使用 css 属性来实现它,我不得不说这是一种更清洁的方法,不知道为什么我没有想到它。发生了很多事情。做得好。我有复选框和一个几乎完成的输入版本,我也试着起床。我已经测试了我的几个地方,但由于某种原因它不想在小提琴中工作。 很高兴你喜欢它。我看到你在你的版本中使用了边界半径。第三种选择是使用 CanvasSurface,如果您已经在使用画布,这实际上更简单。在我的应用程序中,我需要这两种类型,因为我有一些自定义画布控件和一些我想要波纹的常规表面。我能够复制大部分逻辑。在画布渲染循环中,您在偏移点绘制一个完整的弧,并简单地给它一个 rgba 填充样式。结果比径向渐变更干净,但对我来说,并不是每个按钮都实用。 我在玩你的。我想要阴影以及禁用启用按钮的能力。 jsfiddle.net/cjalatorre/zr2m5d88 关于为什么数组连接不起作用而不是您为径向梯度所做的字符串连接的任何想法。 您更新的小提琴不适用于触摸。触摸不会传回偏移量。它正在处理我之前发布的小提琴【参考方案2】:

Clay Awesome 作品喜欢你的版本,我可能会稍微调整一下,用它代替我自己的。

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

【讨论】:

【参考方案3】:

更新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;
);

【讨论】:

以上是关于如何实现google paper按钮效果的主要内容,如果未能解决你的问题,请参考以下文章

如何使用位图实现按钮式效果

鼠标放在按钮上按钮就跑掉的效果是如何实现(vb)

C#,WINFORM中如何实现类似WORD工具栏中选择颜色按钮的效果?

如何jQuery实现图片轮播的同时左右按钮可以实现切换?

asp.net中点击按钮页面不刷新的效果如何实现~~!求详细!

报表如何实现行列互换效果?