From fd06f072f6a6e71829c4221798358117ce55db10 Mon Sep 17 00:00:00 2001 From: "cristobalchao@google.com" Date: Thu, 9 Feb 2017 12:58:16 -0500 Subject: [PATCH] feat(ripple): Implement improved graceful degradation for ripple Resolves #189 --- demos/button.html | 76 +++++++++++++++++++++++- demos/ripple.html | 87 +++++++++++++++++++++------ packages/mdc-button/mdc-button.scss | 92 +++++++++++------------------ packages/mdc-ripple/README.md | 35 +++-------- packages/mdc-ripple/_mixins.scss | 60 ++++++++++++++++--- packages/mdc-ripple/mdc-ripple.scss | 43 ++++---------- 6 files changed, 246 insertions(+), 147 deletions(-) diff --git a/demos/button.html b/demos/button.html index 93ee339bb5b..9f924079b7a 100644 --- a/demos/button.html +++ b/demos/button.html @@ -74,6 +74,42 @@

MDC WEB BUTTON

Div +
+ Graceful degraded buttons + + + + + + + + + + +
+ Div +
+
Links with Button Style @@ -183,6 +219,42 @@

Dark theme

Div
+
+ Graceful degraded buttons + + + + + + + + + + +
+ Div +
+
Disabled Buttons + +
+

Applied to <button> element - Graceful degraded

+ +
+ diff --git a/packages/mdc-button/mdc-button.scss b/packages/mdc-button/mdc-button.scss index 29e99c5126a..91c6baf630e 100644 --- a/packages/mdc-button/mdc-button.scss +++ b/packages/mdc-button/mdc-button.scss @@ -22,6 +22,39 @@ @import "@material/typography/mixins"; // postcss-bem-linter: define button + +.mdc-button { + @include mdc-ripple-base; + @include mdc-ripple-bg((pseudo: "::before")); + @include mdc-ripple-fg((pseudo: "::after")); + + overflow: hidden; + + @include mdc-theme-dark(".mdc-button", true) { + @include mdc-ripple-base; + @include mdc-ripple-bg((pseudo: "::before", base-color: white, opacity: .14)); + @include mdc-ripple-fg((pseudo: "::after", base-color: white, opacity: .14)); + } + + @each $theme-style in (primary, accent) { + &.mdc-button--#{$theme-style} { + @include mdc-ripple-base; + @include mdc-ripple-bg((pseudo: "::before", theme-style: $theme-style, opacity: .12)); + @include mdc-ripple-fg((pseudo: "::after", theme-style: $theme-style, opacity: .12)); + } + } +} + +.mdc-button--raised { + @each $theme-style in (primary, accent) { + &.mdc-button--#{$theme-style} { + @include mdc-ripple-base; + @include mdc-ripple-bg((pseudo: "::before", base-color: white, opacity: .14)); + @include mdc-ripple-fg((pseudo: "::after", base-color: white, opacity: .14)); + } + } +} + .mdc-button { @include mdc-typography(body2); @include mdc-theme-prop(color, text-primary-on-light); @@ -54,37 +87,6 @@ } // postcss-bem-linter: ignore - &::before { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - transition: mdc-animation-exit(opacity, 120ms); - border-radius: inherit; - background: currentColor; - content: ""; - opacity: 0; - } - - &:focus::before { - transition: mdc-animation-enter(opacity, 120ms); - opacity: .12; - } - - &:active::before { - transition: mdc-animation-enter(opacity, 120ms); - - // Slightly darker value for visual distinction. - // This allows a full base that has distinct modes. - // Progressive enhancement with ripples will provide complete button spec alignment. - opacity: .18; - } - - &:focus:active::before { - transition-timing-function: $mdc-animation-fast-out-slow-in-timing-function; - } - &:active { outline: none; } @@ -196,31 +198,3 @@ // postcss-bem-linter: end -.mdc-button.mdc-ripple-upgraded { - @include mdc-ripple-base; - @include mdc-ripple-bg((pseudo: "::before")); - @include mdc-ripple-fg((pseudo: "::after")); - - overflow: hidden; - - @include mdc-theme-dark(".mdc-button", true) { - @include mdc-ripple-bg((pseudo: "::before", base-color: white, opacity: .14)); - @include mdc-ripple-fg((pseudo: "::after", base-color: white, opacity: .14)); - } - - @each $theme-style in (primary, accent) { - &.mdc-button--#{$theme-style} { - @include mdc-ripple-bg((pseudo: "::before", theme-style: $theme-style, opacity: .12)); - @include mdc-ripple-fg((pseudo: "::after", theme-style: $theme-style, opacity: .12)); - } - } -} - -.mdc-button--raised.mdc-ripple-upgraded { - @each $theme-style in (primary, accent) { - &.mdc-button--#{$theme-style} { - @include mdc-ripple-bg((pseudo: "::before", base-color: white, opacity: .14)); - @include mdc-ripple-fg((pseudo: "::after", base-color: white, opacity: .14)); - } - } -} diff --git a/packages/mdc-ripple/README.md b/packages/mdc-ripple/README.md index a68d16db49e..23d9ee2e62c 100644 --- a/packages/mdc-ripple/README.md +++ b/packages/mdc-ripple/README.md @@ -30,6 +30,9 @@ to provide components (or any element at all) with a material "ink ripple" interaction effect. It is designed to be efficient, un-invasive, and usable without adding any extra DOM to your elements. +and includes a gracefully degraded version that can be used +in conjunction with the browser's native element + ### An aside regarding browser support In order to function correctly, MDC Ripple requires a _browser_ implementation of [CSS Variables](https://www.w3.org/TR/css-variables/). MDC Ripple uses custom properties to dynamically position pseudo elements, which allows us to not need any extra DOM for this effect. @@ -39,8 +42,6 @@ Because we rely on scoped, dynamic CSS variables, static pre-processors such as [Most modern browsers](http://caniuse.com/#feat=css-variables) support CSS variables, so MDC ripple will work just fine. In other cases, MDC ripple will _still work_ if you include it in your codebase. It will simply check if CSS variables are supported upon initialization and if they aren't, gracefully exit. The only exception to this rule is Safari, which does support CSS variables but unfortunately ripples are disabled for (see [below](#caveat-safari) for an explanation). -Given this, it is important that you _provide gracefully degraded interaction states_ for browsers in which the ripple is not supported. We do this for all of our components. - ## Installation @@ -74,41 +75,17 @@ use [mdc-elevation](https://github.com/material-components/material-components-w text-align: center; /* Indicate to user element is interactive. */ cursor: pointer; - - - /* Use the surface's ::before pseudo-element as a basic interaction indicator */ - - &::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - pointer-events: none; - background-color: black; - transition: opacity 110ms ease; - will-change: opacity; - } - - &:active::before { - opacity: .18; - } - - &:focus::before { - opacity: .06; - } -} ``` #### Adding the ripple Sass -When a ripple is successfully initialized on an element, it dynamically adds a `mdc-ripple-upgraded` class to that element. Therefore, to add a ripple to our surface, first we include the proper Sass mixins within our surface's styles when it contains this class. We also add a few additional properties that ensure the ripple's UX is correct. +To add a ripple to our surface, first we include the proper Sass mixins within our surface's styles when it contains this class. We also add a few additional properties that ensure the ripple's UX is correct. ```scss @import "@material/elevation/mixins"; @import "@material/ripple/mixins"; -.surface.mdc-ripple-upgraded { +.surface { @include mdc-ripple-base; @include mdc-ripple-bg((pseudo: "::before")); @include mdc-ripple-fg((pseudo: "::after")); @@ -121,6 +98,8 @@ When a ripple is successfully initialized on an element, it dynamically adds a ` This code sets up `.surface` with the correct css variables as well as `will-change` properties to support the ripple. It then dynamically generates the correct selectors such that the surface's `::before` element functions as a background ripple, and the surface's `::after` element functions as a foreground ripple. +When a ripple is successfully initialized on an element, it dynamically adds a `mdc-ripple-upgraded` class to that element. If ripple is not initialized but Sass misins are included within our surface, the ripple would be on a graceful degraded state. + ##### The full Sass API Both `mdc-ripple-bg` and `mdc-ripple-fg` take an `$config` map as an optional diff --git a/packages/mdc-ripple/_mixins.scss b/packages/mdc-ripple/_mixins.scss index 9cd91a85daf..05a73183ff8 100644 --- a/packages/mdc-ripple/_mixins.scss +++ b/packages/mdc-ripple/_mixins.scss @@ -42,6 +42,13 @@ will-change: transition, opacity; -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + + &:hover::before, + &:focus::before, + &:active::after { + transition-duration: 85ms; + opacity: .6; + } } @mixin mdc-ripple-color_($config) { @@ -65,6 +72,7 @@ } @else { background-color: rgba($base-color, $opacity); } + // stylelint-enable at-rule-empty-line-before, block-closing-brace-newline-after } @@ -79,7 +87,7 @@ width: $radius * 2; height: $radius * 2; transform: scale(var(--mdc-ripple-fg-scale)); - transition: opacity 200ms linear; + transition: opacity 250ms linear; border-radius: 50%; opacity: 0; pointer-events: none; @@ -101,6 +109,26 @@ } // stylelint-enable at-rule-empty-line-before, block-closing-brace-newline-after + &.mdc-ripple-upgraded { + &#{$pseudo} { + transition-duration: 0ms; + } + + &:active#{$pseudo}, + &:focus#{$pseudo} { + opacity: 0; + } + + &:active:hover#{$pseudo}, + &:focus:hover#{$pseudo} { + opacity: .6; + } + + &.mdc-ripple-upgraded--background-active#{$pseudo} { + opacity: .99999; + } + } + &.mdc-ripple-upgraded--background-active#{$pseudo} { opacity: .99999; } @@ -126,15 +154,17 @@ } @mixin mdc-ripple-fg-base_($config) { + $radius: map-get($config, radius); + @include mdc-ripple-color_($config); position: absolute; - top: 0; - left: 0; - width: var(--mdc-ripple-fg-size); - height: var(--mdc-ripple-fg-size); - transform: scale(0); - transform-origin: center center; + top: calc(50% - #{$radius}); + left: calc(50% - #{$radius}); + width: $radius * 2; + height: $radius * 2; + transform: scale(var(--mdc-ripple-fg-scale)); + transition: opacity 250ms linear; border-radius: 50%; opacity: 0; pointer-events: none; @@ -156,6 +186,22 @@ } // stylelint-enable at-rule-empty-line-before, block-closing-brace-newline-after + &.mdc-ripple-upgraded { + &#{$pseudo} { + top: 0; + left: 0; + width: var(--mdc-ripple-fg-size); + height: var(--mdc-ripple-fg-size); + transform: scale(0); + transform-origin: center center; + transition-duration: 0ms; + } + + &:active#{$pseudo} { + opacity: 0; + } + } + &:not(.mdc-ripple-upgraded--unbounded)#{$pseudo} { transform-origin: center center; } diff --git a/packages/mdc-ripple/mdc-ripple.scss b/packages/mdc-ripple/mdc-ripple.scss index 22b67e02ce4..b7fa239a1e9 100644 --- a/packages/mdc-ripple/mdc-ripple.scss +++ b/packages/mdc-ripple/mdc-ripple.scss @@ -21,6 +21,10 @@ // postcss-bem-linter: define ripple-surface .mdc-ripple-surface { + @include mdc-ripple-base; + @include mdc-ripple-bg((pseudo: "::before")); + @include mdc-ripple-fg((pseudo: "::after")); + position: relative; outline: none; overflow: hidden; @@ -29,51 +33,24 @@ overflow: visible; } - &::before { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - transition: mdc-animation-exit(opacity, 120ms); - border-radius: inherit; - background: currentColor; - content: ""; - opacity: 0; - } - - &:focus::before { - opacity: .12; - } - - &:active::before { - opacity: .18; - } - - &.mdc-ripple-upgraded { - @include mdc-ripple-base; - @include mdc-ripple-bg((pseudo: "::before")); - @include mdc-ripple-fg((pseudo: "::after")); - } - - &--primary.mdc-ripple-upgraded { + &--primary { &::before, &::after { @include mdc-theme-prop(background-color, primary); } - @include mdc-ripple-bg((pseudo: "::before", theme-style: primary, opacity: .14)); - @include mdc-ripple-fg((pseudo: "::after", theme-style: primary, opacity: .14)); + @include mdc-ripple-bg((pseudo: "::before", theme-style: primary, opacity: .16)); + @include mdc-ripple-fg((pseudo: "::after", theme-style: primary, opacity: .16)); } - &--accent.mdc-ripple-upgraded { + &--accent { &::before, &::after { @include mdc-theme-prop(background-color, primary); } - @include mdc-ripple-bg((pseudo: "::before", theme-style: accent, opacity: .14)); - @include mdc-ripple-fg((pseudo: "::after", theme-style: accent, opacity: .14)); + @include mdc-ripple-bg((pseudo: "::before", theme-style: accent, opacity: .16)); + @include mdc-ripple-fg((pseudo: "::after", theme-style: accent, opacity: .16)); } }