Страницы

Поиск по вопросам

пятница, 29 ноября 2019 г.

Как создать эффект пульсации при клике - `Material Design`

#javascript #html #jquery #css #css3


Я новичок в CSS-анимации, и  стараюсь в течение последних часов, чтобы  анимация
заработала. Пытаюсь понять код Material Design, но пока  не могу заставить его работать.  

Я говорю об этом эффекте: https://angular.io/ (эффект меню). В принципе, это анимация
при клике, которая распространяется по кругу от курсора мыши.   

Кажется, это сводится к этим двум строкам:  

transition: box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1),-webkit-transform
.4s cubic-bezier(.25,.8,.25,1);
transition: box-shadow .4s cubic-bezier(.25,.8,.25,1),background-color .4s cubic-bezier(.25,.8,.25,1),transform
.4s cubic-bezier(.25,.8,.25,1);   


PS: Может быть, есть какой-то код jQuery, который реализует эту анимацию.    

Перевод ответа: How to create Ripple effect on Click - Material Design @topleft
    


Ответы

Ответ 1



Вариант с CSS-переменными, которые используются для раздельного использования свойства transform. Разделять это свойство понадобилось для того, чтобы правильно перемещать элемент (translate3d) и независимо от перемещения масштабировать (scale). Через JS так можно очень удобно управлять этими параметрами. С точки зрения производительности это достаточно хорошая анимация такого эффекта, поскольку не делаются вставки и удаления узлов в DOM и вся работа происходит исключительно со свойствами transform и opacity, которые не влияют на композитный слой, а значит не вызывают множество перерисовок страницы. let span = document.querySelector('button span'); document.querySelector('button').addEventListener('click', function(e) { span.style.setProperty('--x', e.clientX - this.getBoundingClientRect().left - span.offsetWidth/2 + 'px'); span.style.setProperty('--y', e.clientY - this.getBoundingClientRect().top - span.offsetHeight/2 + 'px'); let scaleCount = 0, opacityCount = 1; const animationTime = 500; let scaleUp = setInterval(function() { scaleCount += 0.25; span.style.setProperty('--scale', scaleCount); opacityCount -= 0.05; span.style.opacity = opacityCount; }, animationTime / 20); setTimeout(function() { clearInterval(scaleUp); span.style.setProperty('--scale', 0); }, animationTime); }); body { --x: 0px; --y: 0px; --scale: 0; } button { padding: 10px 20px; position: relative; overflow: hidden; border: 0; border-radius: 3px; background: linear-gradient(#c4e2fa, #c4effa); outline: 0; font: bold 17px arial; text-shadow: 0 0 3px rgba(0,0,0,0.3); color: #FFF; } button:hover { background: linear-gradient(#c4e2fa, #c4e8fa); } span { position: absolute; width: 30px; height: 30px; border-radius: 50%; background-color: rgba(0, 0, 0, 0.3); top: 0; left: 0; transform: translate3d(var(--x), var(--y), 0) scale(var(--scale)); }

Ответ 2



Эффект пульсации в Material Design с использованием jQuery и CSS3 jsBin demo Чтобы создать UI Ripple effect, вам необходимо: Добавить к любому элементу oveflow:hidden, чтобы ограничить круг пульсаций (так как вы не хотите изменять ваш исходный элемент и поэтому с помощью overflow не увидите, что эффект пульсации выходит за пределы желаемого контейнера) Добавить к контейнеру c overflow просвечивающий радиальный элемент ripple wave Взять координаты щелчка мышки, и с помощью CSS3 оживить масштабирование и непрозрачность ripple element Прослушайте событие - анимация и уничтожьте пульсацию. Основной код: В основном добавьте data-ripple (по умолчанию - белая рябь) или data-ripple = "# 000" для нужного элемента: EDIT
Lorem ipsum
CSS: /* MAD-RIPPLE EFFECT */ .ripple{ position: absolute; top:0; left:0; bottom:0; right:0; overflow: hidden; -webkit-transform: translateZ(0); /* to contain zoomed ripple */ transform: translateZ(0); border-radius: inherit; /* inherit from parent (rounded buttons etc) */ pointer-events: none; /* allow user interaction */ animation: ripple-shadow 0.4s forwards; -webkit-animation: ripple-shadow 0.4s forwards; } .rippleWave{ backface-visibility: hidden; position: absolute; border-radius: 50%; transform: scale(0.7); -webkit-transform: scale(0.7); background: rgba(255,255,255, 1); opacity: 0.45; animation: ripple 2s forwards; -webkit-animation: ripple 2s forwards; } @keyframes ripple-shadow { 0% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} 20% {box-shadow: 0 4px 16px rgba(0,0,0,0.3);} 100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} } @-webkit-keyframes ripple-shadow { 0% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} 20% {box-shadow: 0 4px 16px rgba(0,0,0,0.3);} 100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} } @keyframes ripple { to {transform: scale(24); opacity:0;} } @-webkit-keyframes ripple { to {-webkit-transform: scale(24); opacity:0;} } jQuery jQuery(function($) { // MAD-RIPPLE // (jQ+CSS) $(document).on("mousedown", "[data-ripple]", function(e) { var $self = $(this); if($self.is(".btn-disabled")) { return; } if($self.closest("[data-ripple]")) { e.stopPropagation(); } var initPos = $self.css("position"), offs = $self.offset(), x = e.pageX - offs.left, y = e.pageY - offs.top, dia = Math.min(this.offsetHeight, this.offsetWidth, 100), // start diameter $ripple = $('
', {class : "ripple",appendTo : $self }); if(!initPos || initPos==="static") { $self.css({position:"relative"}); } $('
', { class : "rippleWave", css : { background: $self.data("ripple"), width: dia, height: dia, left: x - (dia/2), top: y - (dia/2), }, appendTo : $ripple, one : { animationend : function(){ $ripple.remove(); } } }); }); }); Вот полнофункциональная демонстрация: jQuery(function($) { // MAD-RIPPLE // (jQ+CSS) $(document).on("mousedown", "[data-ripple]", function(e) { var $self = $(this); if($self.is(".btn-disabled")) { return; } if($self.closest("[data-ripple]")) { e.stopPropagation(); } var initPos = $self.css("position"), offs = $self.offset(), x = e.pageX - offs.left, y = e.pageY - offs.top, dia = Math.min(this.offsetHeight, this.offsetWidth, 100), // start diameter $ripple = $('
', {class : "ripple",appendTo : $self }); if(!initPos || initPos==="static") { $self.css({position:"relative"}); } $('
', { class : "rippleWave", css : { background: $self.data("ripple"), width: dia, height: dia, left: x - (dia/2), top: y - (dia/2), }, appendTo : $ripple, one : { animationend : function(){ $ripple.remove(); } } }); }); }); *{box-sizing:border-box; -webkit-box-sizing:border-box;} html, body{height:100%; margin:0;} body{background:#f5f5f5; font: 14px/20px Roboto, sans-serif;} h1, h2{font-weight: 300;} /* MAD-RIPPLE EFFECT */ .ripple{ position: absolute; top:0; left:0; bottom:0; right:0; overflow: hidden; -webkit-transform: translateZ(0); /* to contain zoomed ripple */ transform: translateZ(0); border-radius: inherit; /* inherit from parent (rounded buttons etc) */ pointer-events: none; /* allow user interaction */ animation: ripple-shadow 0.4s forwards; -webkit-animation: ripple-shadow 0.4s forwards; } .rippleWave{ backface-visibility: hidden; position: absolute; border-radius: 50%; transform: scale(0.7); -webkit-transform: scale(0.7); background: rgba(255,255,255, 1); opacity: 0.45; animation: ripple 2s forwards; -webkit-animation: ripple 2s forwards; } @keyframes ripple-shadow { 0% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} 20% {box-shadow: 0 4px 16px rgba(0,0,0,0.3);} 100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} } @-webkit-keyframes ripple-shadow { 0% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} 20% {box-shadow: 0 4px 16px rgba(0,0,0,0.3);} 100% {box-shadow: 0 0 0 rgba(0,0,0,0.0);} } @keyframes ripple { to {transform: scale(24); opacity:0;} } @-webkit-keyframes ripple { to {-webkit-transform: scale(24); opacity:0;} } /* MAD-BUTTONS (demo) */ [class*=mad-button-]{ display:inline-block; text-align:center; position: relative; margin: 0; white-space: nowrap; vertical-align: middle; font-family: "Roboto", sans-serif; font-size: 14px; font-weight: 500; text-transform: uppercase; text-decoration: none; border: 0; outline: 0; background: none; transition: 0.3s; cursor: pointer; color: rgba(0,0,0, 0.82); } [class*=mad-button-] i.material-icons{ vertical-align:middle; padding:0; } .mad-button-raised{ height: 36px; padding: 0px 16px; line-height: 36px; border-radius: 2px; box-shadow: /*amb*/ 0 0 2px rgba(0,0,0,0.15), /*key*/ 0 1px 3px rgba(0,0,0,0.25); }.mad-button-raised:hover{ box-shadow: /*amb*/ 0 0 2px rgba(0,0,0,0.13), /*key*/ 0 2px 4px rgba(0,0,0,0.2); } .mad-button-action{ width: 56px; height:56px; padding: 16px 0; border-radius: 32px; box-shadow: /*amb*/ 0 0 2px rgba(0,0,0,0.13), /*key*/ 0 5px 7px rgba(0,0,0,0.2); }.mad-button-action:hover{ box-shadow: /*amb*/ 0 0 2px rgba(0,0,0,0.11), /*key*/ 0 6px 9px rgba(0,0,0,0.18); } [class*=mad-button-].mad-ico-left i.material-icons{ margin: 0 8px 0 -4px; } [class*=mad-button-].mad-ico-right i.material-icons{ margin: 0 -4px 0 8px; } /* MAD-COLORS */ .bg-primary-darker{background:#1976D2; color:#fff;} .bg-primary{ background:#2196F3; color:#fff; } .bg-primary.lighter{ background: #BBDEFB; color: rgba(0,0,0,0.82);} .bg-accented{ background:#FF4081; color:#fff; } /* MAD-CELL */ .cell{padding: 8px 16px; overflow:auto;}
search

Click to Ripple

data-ripple

data-ripple="rgba(0,0,0, 0.4)"

Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore....

Editedit

Перевод ответа: How to create Ripple effect on Click - Material Design @Roko C. Buljan

Ответ 3



На чистом js + babel - javascript - class ImpulseStyleFactory { static ANIMATION_DEFAULT_DURATION = 1; static ANIMATION_DEFAULT_SIZE = 300; static ANIMATION_RATIO = ImpulseStyleFactory.ANIMATION_DEFAULT_DURATION / ImpulseStyleFactory.ANIMATION_DEFAULT_SIZE; static circleImpulseStyle( x, y, size, color = `#fff`, duration = 1 ){ return { width: `${ size }px`, height: `${ size }px`, background: color, borderRadius: `50%`, display: `inline-block`, pointerEvents: `none`, position: `absolute`, top: `${ y - size / 2 }px`, left: `${ x - size / 2 }px`, animation: `impulse ${ duration }s`, }; } } class Impulse { static service = new Impulse(); static install( container ) { Impulse.service.containerRegister( container ); } static destroy( container ){ Impulse.service.containerUnregister( container ); } static applyToElement( {x, y}, container ){ Impulse.service.createImpulse( x, y, container ); } constructor(){ this.impulse_clickHandler = this.impulse_clickHandler.bind(this); this.impulse_animationEndHandler = this.impulse_animationEndHandler.bind(this); this.actives = new Map(); } containerRegister( container ){ container.addEventListener('click', this.impulse_clickHandler); } containerUnregister( container ){ container.removeEventListener('click', this.impulse_clickHandler); } createImpulse( x, y, container ){ let { clientWidth, clientHeight } = container; let impulse = document.createElement('div'); impulse.addEventListener('animationend', this.impulse_animationEndHandler); let size = Math.max( clientWidth, clientHeight ) * 2; let color = container.dataset.color; Object.assign(impulse.style, ImpulseStyleFactory.circleImpulseStyle( x, y, size, color )); if( this.actives.has( container ) ){ this.actives.get( container ) .add( impulse ); }else{ this.actives.set( container, new Set( [ impulse ] ) ); } container.dataset.active = true; container.appendChild( impulse ); } impulse_clickHandler({ layerX, layerY, currentTarget: container }){ this.createImpulse( layerX, layerY, container ); } impulse_animationEndHandler( {currentTarget: impulse} ){ let { parentNode: container } = impulse; this.actives.get( container ) .delete( impulse ); if( ! this.actives.get( container ).size ){ this.actives.delete( container ); container.dataset.active = false; } container.removeChild(impulse); } } css - @keyframes impulse { from { opacity: .3; transform: scale(0); } to { opacity: 0; transform: scale(1); } } использовать так - html -
javascript - let impulses = document.querySelectorAll('.impulse'); let impulseAll = Array.from( impulses ); impulseAll.forEach( Impulse.install ); Живой пример с Impulse.install ( импульс создается в координатах клика, навешивается слушатель события click ) - class ImpulseStyleFactory { static ANIMATION_DEFAULT_DURATION = 1; static ANIMATION_DEFAULT_SIZE = 300; static ANIMATION_RATIO = ImpulseStyleFactory.ANIMATION_DEFAULT_DURATION / ImpulseStyleFactory.ANIMATION_DEFAULT_SIZE; static circleImpulseStyle( x, y, size, color = `#fff`, duration = 1 ){ return { width: `${ size }px`, height: `${ size }px`, background: color, borderRadius: `50%`, display: `inline-block`, pointerEvents: `none`, position: `absolute`, top: `${ y - size / 2 }px`, left: `${ x - size / 2 }px`, animation: `impulse ${ duration }s`, }; } } class Impulse { static service = new Impulse(); static install( container ) { Impulse.service.containerRegister( container ); } static destroy( container ){ Impulse.service.containerUnregister( container ); } static applyToElement( {x, y}, container ){ Impulse.service.createImpulse( x, y, container ); } constructor(){ this.impulse_clickHandler = this.impulse_clickHandler.bind(this); this.impulse_animationEndHandler = this.impulse_animationEndHandler.bind(this); this.actives = new Map(); } containerRegister( container ){ container.addEventListener('click', this.impulse_clickHandler); } containerUnregister( container ){ container.removeEventListener('click', this.impulse_clickHandler); } createImpulse( x, y, container ){ let { clientWidth, clientHeight } = container; let impulse = document.createElement('div'); impulse.addEventListener('animationend', this.impulse_animationEndHandler); let size = Math.max( clientWidth, clientHeight ) * 2; let color = container.dataset.color; Object.assign(impulse.style, ImpulseStyleFactory.circleImpulseStyle( x, y, size, color )); if( this.actives.has( container ) ){ this.actives.get( container ) .add( impulse ); }else{ this.actives.set( container, new Set( [ impulse ] ) ); } container.dataset.active = true; container.appendChild( impulse ); } impulse_clickHandler({ layerX, layerY, currentTarget: container }){ this.createImpulse( layerX, layerY, container ); } impulse_animationEndHandler( {currentTarget: impulse} ){ let { parentNode: container } = impulse; this.actives.get( container ) .delete( impulse ); if( ! this.actives.get( container ).size ){ this.actives.delete( container ); container.dataset.active = false; } container.removeChild(impulse); } } let impulses = document.querySelectorAll('.impulse'); let impulseAll = Array.from( impulses ); impulseAll.forEach( Impulse.install ); @import "https://cdnjs.cloudflare.com/ajax/libs/normalize/6.0.0/normalize.min.css"; /*@import url('https://fonts.googleapis.com/css?family=Roboto+Mono');*/ * { box-sizing: border-box; } html { font-family: 'Roboto Mono', monospace; } body { width: 100%; height: 100%; margin: 0; position: absolute; } main { width: 100%; height: 100%; overflow: hidden; position: relative; } .container { position: absolute; top: 0; left: 0; } .centred { display: flex; justify-content: center; align-items: center; } .shadow-xs { box-shadow: rgba(0, 0, 0, 0.117647) 0px 1px 6px, rgba(0, 0, 0, 0.117647) 0px 1px 4px; } .sample-impulse { transition: all .5s; overflow: hidden; position: relative; } .sample-impulse[data-active="true"] { box-shadow: rgba(0, 0, 0, 0.156863) 0px 3px 10px, rgba(0, 0, 0, 0.227451) 0px 3px 10px; } .panel { width: 300px; height: 100px; background: #fff; } .panel__hidden-label { color: #fff; font-size: 2rem; font-weight: bold; pointer-events: none; z-index: 1; position: absolute; } .panel__default-label { pointer-events: none; z-index: 2; position: absolute; } .sample-impulse[data-active="true"] .panel__default-label { display: none; } @keyframes impulse { from { opacity: .3; transform: scale(0); } to { opacity: 0; transform: scale(1); } }
StackOverflow click me
Живой пример с Impulse.applyToElement ( координаты импульса задаются вручную, слушатель события click не навешивается ) - class ImpulseStyleFactory { static ANIMATION_DEFAULT_DURATION = 1; static ANIMATION_DEFAULT_SIZE = 300; static ANIMATION_RATIO = ImpulseStyleFactory.ANIMATION_DEFAULT_DURATION / ImpulseStyleFactory.ANIMATION_DEFAULT_SIZE; static circleImpulseStyle( x, y, size, color = `#fff`, duration = 1 ){ return { width: `${ size }px`, height: `${ size }px`, background: color, borderRadius: `50%`, display: `inline-block`, pointerEvents: `none`, position: `absolute`, top: `${ y - size / 2 }px`, left: `${ x - size / 2 }px`, animation: `impulse ${ duration }s`, }; } } class Impulse { static service = new Impulse(); static install( container ) { Impulse.service.containerRegister( container ); } static destroy( container ){ Impulse.service.containerUnregister( container ); } static applyToElement( {x, y}, container ){ Impulse.service.createImpulse( x, y, container ); } constructor(){ this.impulse_clickHandler = this.impulse_clickHandler.bind(this); this.impulse_animationEndHandler = this.impulse_animationEndHandler.bind(this); this.actives = new Map(); } containerRegister( container ){ container.addEventListener('click', this.impulse_clickHandler); } containerUnregister( container ){ container.removeEventListener('click', this.impulse_clickHandler); } createImpulse( x, y, container ){ let { clientWidth, clientHeight } = container; let impulse = document.createElement('div'); impulse.addEventListener('animationend', this.impulse_animationEndHandler); let size = Math.max( clientWidth, clientHeight ) * 2; let color = container.dataset.color; Object.assign(impulse.style, ImpulseStyleFactory.circleImpulseStyle( x, y, size, color )); if( this.actives.has( container ) ){ this.actives.get( container ) .add( impulse ); }else{ this.actives.set( container, new Set( [ impulse ] ) ); } container.dataset.active = true; container.appendChild( impulse ); } impulse_clickHandler({ layerX, layerY, currentTarget: container }){ this.createImpulse( layerX, layerY, container ); } impulse_animationEndHandler( {currentTarget: impulse} ){ let { parentNode: container } = impulse; this.actives.get( container ) .delete( impulse ); if( ! this.actives.get( container ).size ){ this.actives.delete( container ); container.dataset.active = false; } container.removeChild(impulse); } } const generateRandomPointByRectdAll = ( { width, height }, length = 1 ) => { let result = []; while( length-- ){ result.push( { x: Math.round( Math.random() * width ), y: Math.round( Math.random() * height ) } ); } return result; }; const delayTask = ( task, delay ) => new Promise( ( resolve, reject ) => { let timeoutID = setTimeout( () => task( ), delay ) } ); document.addEventListener( 'click', () => { const MAX_IMPULSE_DELAY_TIME = 5000; let container = document.querySelector('.custom-impulse'); let pointAll = generateRandomPointByRectdAll( { width: container.clientWidth, height: container.clientHeight }, 5 ); let taskAll = pointAll.map( point => () => Impulse.applyToElement( point, container ) ); let delayTaskAll = taskAll.map( task => delayTask( task, Math.round( Math.random() * MAX_IMPULSE_DELAY_TIME ) ) ); } ); @import "https://cdnjs.cloudflare.com/ajax/libs/normalize/6.0.0/normalize.min.css"; /*@import url('https://fonts.googleapis.com/css?family=Roboto+Mono');*/ * { box-sizing: border-box; } html { font-family: 'Roboto Mono', monospace; } body { width: 100%; height: 100%; margin: 0; position: absolute; } main { width: 100%; height: 100%; overflow: hidden; position: relative; } .container-fill { width: 100%; height: 100%; } .container { position: absolute; top: 0; left: 0; } .centred { display: flex; justify-content: center; align-items: center; } .custom-impulse { will-change: transform, opasity; position: absolute; } @keyframes impulse { from { opacity: .3; transform: scale(0); } to { opacity: 0; transform: scale(1); } }
click me


Ответ 4



Я использовал этот код раньше в нескольких моих проектах. Используя jQuery, мы можем поместить эффект на него не просто статически, но и затем добавить на элемент span onclick. Я добавил комментарии, чтобы было проще понять код. Demo Here jQuery $("div").click(function (e) { // Remove any old one $(".ripple").remove(); // Setup var posX = $(this).offset().left, posY = $(this).offset().top, buttonWidth = $(this).width(), buttonHeight = $(this).height(); // Add the element $(this).prepend(""); // Make it round! if(buttonWidth >= buttonHeight) { buttonHeight = buttonWidth; } else { buttonWidth = buttonHeight; } // Get the center of the element var x = e.pageX - posX - buttonWidth / 2; var y = e.pageY - posY - buttonHeight / 2; // Add the ripples CSS and start the animation $(".ripple").css({ width: buttonWidth, height: buttonHeight, top: y + 'px', left: x + 'px' }).addClass("rippleEffect"); }); CSS .ripple { width: 0; height: 0; border-radius: 50%; background: rgba(255, 255, 255, 0.4); transform: scale(0); position: absolute; opacity: 1; } .rippleEffect { animation: rippleDrop .6s linear; } @keyframes rippleDrop { 100% { transform: scale(2); opacity: 0; } } Перевод ответа: How to create Ripple effect on Click - Material Design @Ruddy

Ответ 5



Такая анимация может быть достигнута с помощью box-shadows. Размещение начала окружности под курсором мышки, при щелчке мышью потребуется JS. li{ font-size:2em; background:rgba(51, 51, 254, 0.8); list-style-type:none; display:inline-block; line-height:2em; width:6em; text-align:center; color:#fff; position:relative; overflow:hidden; } a{color:#fff;} a:after{ content:''; position:absolute; border-radius:50%; height:10em; width:10em; top: -4em; left:-2em; box-shadow: inset 0 0 0 5em rgba(255,255,255,0.2); transition: box-shadow 0.8s; } a:focus:after{ box-shadow: inset 0 0 0 0em rgba(255,255,255,0.2); } Перевод ответа: How to create Ripple effect on Click - Material Design @web-tiki

Комментариев нет:

Отправить комментарий