I'm new to CSS animations and I've been trying to make their animation work for the last hours by looking at their code, but I can't make it work for now.
I'm talking about this effect: https://angular.io/ (menu effect).
Basically, it's an animation on click that spreads a circle from the mouse cursor.
Seems it comes down to these 2 lines:
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: Maybe there's some jQuery I didn't see.
Ripple effect in Material Design using jQuery and CSS3
To create a UX Ripple effect basically you need to:
append to any element an oveflow:hidden element to contain the ripple circle (you don't want to alter your original element overflow, neither see the ripple effect go outside of a desired container)
append to the overflow container the ripple wave translucent radial element
get the click coordinates and CSS3 animate the scaling and opacity of the ripple element
Listen for the animationend event and destroy the ripple container.
The basic code:
Basically add data-ripple (default as white ripple) or data-ripple="#000" to a desired element:
<a data-ripple> EDIT </a>
<div data-ripple="rgba(0,0,0, 0.3)">Lorem ipsum</div>
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 = $('<div/>', {class : "ripple",appendTo : $self });
if(!initPos || initPos==="static") {
$self.css({position:"relative"});
}
$('<div/>', {
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();
}
}
});
});
});
Here's a full-featured demo:
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 = $('<div/>', {class : "ripple",appendTo : $self });
if(!initPos || initPos==="static") {
$self.css({position:"relative"});
}
$('<div/>', {
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;}
<link href='https://fonts.googleapis.com/css?family=Roboto:500,400,300&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<script src="https://code.jquery.com/jquery-2.1.4.js"></script>
<div class="cell">
<button data-ripple class="mad-button-raised mad-ico-left bg-primary"><i class="material-icons">person</i>User settings</button>
<a data-ripple href="#" class="mad-button-action bg-accented"><i class="material-icons">search</i></a>
</div>
<div data-ripple class="cell bg-primary-darker">
<h1>Click to Ripple</h1>
<p>data-ripple</p>
</div>
<div data-ripple="rgba(0,0,0, 0.4)" class="cell bg-primary">
<p>data-ripple="rgba(0,0,0, 0.4)"</p>
<p> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore....</p>
<p><a data-ripple class="mad-button-raised mad-ico-right bg-accented">Edit<i class="material-icons">edit</i></a></p>
</div>
I have used this sort of code before on a few of my projects.
Using jQuery we can position the effect to its not just static and then we add the span element onclick. I have added comments so it makes it easier to follow.
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("<span class='ripple'></span>");
// 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;
}
}
This can be achieved with box-shadows. The positioning of the circle origin under the mouse when clicked will need 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);
}
<ul>
<li>button</li>
</ul>
Here is a CSS - only implementation i.e. no javascript required.
Source: https://ghinda.net/article/css-ripple-material-design/
body {
background: #fff;
}
button {
position: relative;
overflow: hidden;
padding: 16px 32px;
}
button:after {
content: '';
display: block;
position: absolute;
left: 50%;
top: 50%;
width: 120px;
height: 120px;
margin-left: -60px;
margin-top: -60px;
background: #3f51b5;
border-radius: 100%;
opacity: .6;
transform: scale(0);
}
#keyframes ripple {
0% {
transform: scale(0);
}
20% {
transform: scale(1);
}
100% {
opacity: 0;
transform: scale(1);
}
}
button:not(:active):after {
animation: ripple 1s ease-out;
}
/* fixes initial animation run, without user input, on page load.
*/
button:after {
visibility: hidden;
}
button:focus:after {
visibility: visible;
}
<button>
Button
</button>
You could use http://mladenplavsic.github.io/css-ripple-effect/ (note: I'm the author of that product)
Pure CSS solution
<link href="https://cdn.rawgit.com/mladenplavsic/css-ripple-effect/35c35541/dist/ripple.min.css" rel="stylesheet"/>
<button class="ripple">Click me</button>
You can get the same effect with the help of Materialize css, making it with that is quite easy. All you have to do is just add a class to where you want the effect.
Submit
If you want to go with pure CSS check this codepen it : Ripple effect
Here is Material Design button component "The wave effect" Done Using pure CSS3 and JavaScript no libraries no framework
Material Design button component "The wave effect"
https://codepen.io/Mahmoud-Zakaria/pen/NvbORQ
HTML
<div class="md" >Click</div>
CSS
#keyframes glow-out {
30%,80% {
transform: scale(7);
}
100% {
opacity: 0;
}
}
.md {
--y: 0;
--x: 0;
display: inline-block;
padding: 20px 70px;
text-align: center;
background-color: lightcoral;
margin: 5em;
position: relative;
overflow: hidden;
cursor: pointer;
border-radius: 4px;
color: white;
}
.is-clicked {
content: '';
position: absolute;
top: calc(var(--y) * 1px);
left: calc(var(--x) * 1px);
width: 100px;
height:100px;
background: rgba(255, 255, 255, .3);
border-radius: 50%;
animation: glow-out 1s ease-in-out forwards;
transform: translate(-50%, -50%);
}
JS
// Material Design button Module
let md_module = (function() {
let btn = document.querySelectorAll(".md");
let md_btn = Array.prototype.slice.call(btn);
md_btn.forEach(eachCB)
function eachCB (item, index, array){
function md(e) {
let offsetX = e.clientX - item.offsetLeft;
let offsetY = e.clientY - item.offsetTop;
item.style.setProperty("--x", offsetX);
item.style.setProperty("--y", offsetY);
item.innerHTML += '<div class="is-clicked"></div>';
}
function rm() {
let state = item.querySelectorAll(".is-clicked");
console.log(state)
for (let i = 0; i < state.length; i++) {
if (state[i].className === "is-clicked") {
state[i].remove();
}
}
}
item.addEventListener("click", md);
item.addEventListener("animationend", rm);
}
})();
CSS Paint API (introduced in 2018)
The new CSS Paint API (part of the CSS "Houdini" draft) allows to write JavaScript functions to be used in CSS. Quote of the linked document:
CSS Paint API allows you to programmatically generate an image whenever a CSS property expects an image. Properties like background-image or border-image are usually used with url() to load an image file or with CSS built-in functions like linear-gradient(). Instead of using those, you can now use paint(myPainter) to reference a paint worklet.
This means you can implement a paint function in JavaScript and use it inside your CSS.
Browser support (May 2019)
Currently, only Chrome and Opera support the Paint API of the Houdini draft. Firefox has signaled "intent to implement". See ishoudinireadyyet.com or caniuse.com for more information.
Code sample
There is a working "ripple" example implemented by the Houdini task force available here. I extracted the "core" from the example below. It implements the custom paint function, adds custom CSS properties like --ripple-color and uses a JavaScript function to implement the animation and to start and stop the effect.
Note, that it adds the custom paint function like this:
CSS.paintWorklet.addModule('https://googlechromelabs.github.io/houdini-samples/paint-worklet/ripple/paintworklet.js');
If you want to use the effect on your website, I recommend you download the file and reference it locally.
// Adds the custom paint function
CSS.paintWorklet.addModule('https://googlechromelabs.github.io/houdini-samples/paint-worklet/ripple/paintworklet.js');
// the actual animation of the ripple effect
function rippleEffect(element) {
let start, x, y;
element.addEventListener('click', function (evt) {
this.classList.add('animating');
[x, y] = [evt.offsetX, evt.offsetY];
start = performance.now();
const raf = (now) => {
const tick = Math.floor(now - start);
this.style.cssText = `--ripple-x: ${x}; --ripple-y: ${y}; --animation-tick: ${tick};`;
if (tick > 1000) {
this.classList.remove('animating');
this.style.cssText = `--animation-tick: 0`;
return;
}
requestAnimationFrame(raf);
};
requestAnimationFrame(raf);
});
}
rippleEffect(document.querySelector('.ripple'));
.ripple {
font-size: 5em;
background-color: rgb(0, 169, 244);
/* custom property */
--ripple-color: rgba(255, 255, 255, 0.54);
}
.ripple.animating {
/* usage of the custom "ripple" paint function */
background-image: paint(ripple);
}
<button class="ripple">Click me!</button>
Realization javascript + 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);
}
}
to use so -
html -
<div class="impulse" data-color="#3f1dcb" data-active="false">
<div class="panel"></div>
</div>
javascript -
let impulses = document.querySelectorAll('.impulse');
let impulseAll = Array.from( impulses );
impulseAll.forEach( Impulse.install );
Life example Impulse.install ( impulse create in coords of click, add handler event 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);
}
}
<main class="centred">
<div class="sample-impulse impulse centred shadow-xs" data-color="#3f1dcb" data-active="false">
<div class="group centred">
<div class="panel"></div>
<span class="panel__hidden-label">StackOverflow</span>
<span class="panel__default-label">click me</span>
</div>
</div>
</main>
Life example Impulse.applyToElement ( impulse coords setby user, not add handler event 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);
}
}
<main class="centred">
<div class="custom-impulse container-fill centred" data-color="#3f1dcb" data-active="false">
<span>click me</span>
</div>
</main>
You can use Tronic247 Material framework to make the ripple effect.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>A Basic HTML5 Template</title>
<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/gh/tronic247/material/dist/css/material.min.css" rel="stylesheet" />
</head>
<body class="container">
<div class="background-light-grey elevation-4 ripple-e dark-ripple" style="height: 200px;width: 200px;"></div>
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/tronic247/material/dist/js/material.min.js"></script>
</body>
</html>
Related
Goal
Make the glare layer on all visible children like a clip-path
––––––––––––
Heads Up
Child elements can be any shape with any animation
Child elements can be any svg shape with any kind of animation attached to it
So glare must be automatically dynamic and conforming
––––––––––––
What I've Done
I create an apple TV effect…
But the glare only works as a box on top of other boxes.
The glare does not conform to other shapes
Example Below
––––––––––––
What I Can't Use
Canvas - No Canvas Please - I'm not familiar with it
Clip-Path - Because child elements can be anything overflowing outside of the glare
––––––––––––
What I'm looking for
Some kind of magical CSS line of code that makes the glare layer conform to all elements under it… like a normal glare would work.
Is this possible?
Is there some way Javascript can glare it automatically?
Is there some kind of mix-blend-mode I can use to make the glare just work?
Or is this something that is just impossible?
Glare should not look like a box
––––––––––––
What I tried
I tried to scale the glare layer to scale(1.1) and use some mix-blend-mode
But I couldn't figure out how to make it work.
appleTV();
function appleTV(){
appleTVComponents = 0;
function rotateX(n) {return ' rotateX('+n+'deg)'}
function rotateY(n) {return ' rotateY('+n+'deg)'}
function translateX(n) {return ' translateX('+n+'px)'}
function translateY(n) {return ' translateY('+n+'px)'}
function perspective(n) {return 'perspective('+n+'px)'}
function scale(n) {return ' scale3d('+n+','+n+','+n+')'}
function section(s='',e) {e=document.createElement('section');e.className='appletv_'+s;return e;}
function getWidth(e) {return e.clientWidth || e.offsetWidth || e.scrollWidth}
function setPerspective(e) {e.style.transform = perspective(getWidth(e)*3);}
function preventScroll(state) {if(supportsTouch){win.preventScroll=state||false;}}
function preventDefault(e) {if (supportsTouch&&win.preventScroll){e.preventDefault();}}
function isTouchScreen() {return 'ontouchstart' in window || navigator.msMaxTouchPoints}
function child(e) {return e.firstChild;}
function children(e) {return [...e.children]}
let body = document.body,
win = window,
imgs = document.querySelectorAll('.appletv'),
totalImgs = imgs.length,
supportsTouch = isTouchScreen(),
move = 'mousemove',
start = 'mouseenter',
end = 'mouseleave';
if(supportsTouch){move='touchmove'; start='touchstart'; end='touchend';}
if(totalImgs <= 0){return;}
for(var l=0;l<totalImgs;l++){
var thisImg = imgs[l],
layerElems = [...thisImg.querySelectorAll('.appletv_layer')];
if(!layerElems.length){continue;}
while(thisImg.firstChild) {thisImg.removeChild(thisImg.firstChild);}
var containerHTML = section(''),
shineHTML = section('gloss'),
shadowHTML = section('shadow'),
layersHTML = section('layer'),
layers = [];
thisImg.id = 'appletv_'+(++appleTVComponents);
layerElems.forEach((e,i)=>{
let layer_ = section('rendered_layer')
layer = section(''),
img = e.getAttribute('data-img');
layer_.setAttribute('data-layer',i);
[...e.children].forEach(c=>{layer.appendChild(c)})
if (img) {layer.style.backgroundImage = 'url('+img+')';}
layer_.appendChild(layer);
layersHTML.appendChild(layer_);
layers.push(layer);
});
[shadowHTML,layersHTML,shineHTML].forEach(e=>{containerHTML.appendChild(e)});
thisImg.appendChild(containerHTML);
var w = getWidth(thisImg);
setPerspective(thisImg)
preventScroll();
(function enableMovements(_thisImg,_layers,_totalLayers,_shine) {
thisImg.addEventListener(move, e=>{processMovement(e,supportsTouch,_thisImg,_layers,_totalLayers,_shine);});
thisImg.addEventListener(start, e=>{processEnter(_thisImg);});
thisImg.addEventListener(end, e=>{processExit(_thisImg,_layers,_totalLayers,_shine);});
})(thisImg,layers,layerElems.length,shineHTML);
};
function processMovement(e, touchEnabled, elem, layers, totalLayers, shine){
preventDefault(e)
let bdst = body.scrollTop,
bdsl = body.scrollLeft,
pageX = (touchEnabled)? e.touches[0].pageX : e.pageX,
pageY = (touchEnabled)? e.touches[0].pageY : e.pageY,
offsets = elem.getBoundingClientRect(),
w = elem.clientWidth || elem.offsetWidth || elem.scrollWidth, // width
h = elem.clientHeight || elem.offsetHeight || elem.scrollHeight, // height
wMultiple = 320/w,
offsetX = 0.52 - (pageX - offsets.left - bdsl)/w, //cursor position X
offsetY = 0.52 - (pageY - offsets.top - bdst)/h, //cursor position Y
dy = (pageY - offsets.top - bdst) - h / 2, //#h/2 = center of container
dx = (pageX - offsets.left - bdsl) - w / 2, //#w/2 = center of container
yRotate = (offsetX - dx)*(0.07 * wMultiple), //rotation for container Y
xRotate = (dy - offsetY)*(0.1 * wMultiple), //rotation for container X
imgCSS = rotateX(xRotate)+rotateY(yRotate), //img transform
arad = Math.atan2(dy, dx), //angle between cursor and center of container in RAD
angle = arad * 180 / Math.PI - 90; //convert rad in degrees
if (angle < 0) {angle = angle + 360;}
if(elem.firstChild.className.indexOf(' over') != -1){imgCSS += scale(1.07);}
elem.firstChild.style.transform = imgCSS;
shine.style.background = 'linear-gradient(' + angle + 'deg, rgba(255,255,255,' + (pageY - offsets.top - bdst)/h * 0.4 + ') 0%,rgba(255,255,255,0) 80%)';
shine.style.transform = translateX((offsetX * totalLayers) - 0.1)+translateY((offsetY * totalLayers) - 0.1);
var revNum = totalLayers;
for(var ly=0;ly<totalLayers;ly++){
layers[ly].style.transform = translateX((offsetX * revNum) * ((ly * 2.5) / wMultiple))+translateX((offsetY * totalLayers) * ((ly * 2.5) / wMultiple));
revNum--;
}
}
function processEnter(e){preventScroll(true);setPerspective(e);child(e)&&child(e).classList.add('over');}
function processExit(elem, layers, totalLayers, shine){preventScroll();
child(elem).classList.remove('over')
child(elem).style.transform = '';
shine.style = '';
layers.forEach(e=>{e.style.transform = ''})
}
}
body,
html {
height: 100%;
min-height: 100%;
}
body {background: linear-gradient(to bottom, #f6f7fc 0%, #d5e1e8 40%);}
.center{
position: absolute;
left: 50%;
margin: 10px auto;
transform: translateX(-50%);
}
.appletv {
position: relative !important;
margin: 0 auto !important;
display: inline-block;
width: 300px;
height: 150px;
border-radius: 5px;
transform-style: preserve-3d;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
cursor: pointer;
backface-visibility: hidden;
}
.appletv.depressed {
margin-top: 25px;
box-shadow: 0 5px 30px rgba(0, 0, 0, 0.4);
}
.appletv_ {
position: relative;
width: 100%;
height: 100%;
border-radius: 5px;
transition: all 0.2s ease-out;
background: teal;
}
.appletv_container.over {z-index: 1;}
.appletv_container.over .appletv_shadow {box-shadow: 0 45px 100px rgba(14, 21, 47, 0.4), 0 16px 40px rgba(14, 21, 47, 0.4);}
.appletv_layer {
position: relative;
width: 100%;
height: 100%;
border-radius: 5px;
/*overflow: hidden;*/
transform-style: preserve-3d;
}
.appletv_rendered_layer {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
overflow: hidden;
border-radius: 5px;
transition: all 0.1s ease-out;
transform-style: preserve-3d;
}
.appletv_rendered_layer > :first-child {
position: absolute;
width: 104%;
height: 104%;
top: -2%;
left: -2%;
background-repeat: no-repeat;
background-position: center;
background-color: transparent;
background-size: cover;
transition: all 0.1s ease-out;
}
.appletv_shadow {
position: absolute;
top: 5%;
left: 5%;
width: 90%;
height: 90%;
transition: all 0.2s ease-out;
box-shadow: 0 8px 30px rgba(14, 21, 47, 0.6);
}
.appletv_gloss {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 5px;
/*display: none !important;*/
background: linear-gradient(135deg, rgba(255, 255, 255, 0.25) 0%, rgba(255, 255, 255, 0) 40%);
}
[data-layer="1"] {overflow: visible !important;}
[data-layer="1"] > section > section {
position: absolute;
background: rgb(50, 141, 210);
width: 60px;
height: 60px;
border-radius: 10px;
}
[data-layer="1"] > section > section:first-child {
left: -30px;
top: -10px;
}
[data-layer="1"] > section > section:last-child {
right: -20px;
top: 50px;
}
#keyframes rotate {
0% {transform: rotate(0);}
100% {transform: rotate(359deg);}
}
.appletv_gloss {
/*display: none;*/
background-blend-mode: multiply;
}
.appletv [data-layer="1"] {
transform: scale(0.5);
transition: .3s ease-in-out 0s;
}
.appletv:hover [data-layer="1"] {
transform: scale(1);
}
.appletv:hover [data-layer="1"] > section > section {
animation: rotate 10s linear 0s infinite;
}
.appletv:hover [data-layer="1"] > section > section:last-child {
animation: rotate 25s linear 0s infinite;
}
#hover {
font-size: 30px;
position: absolute;
top: 37%;
text-align: center;
width: 100%;
color: white;
text-shadow: 0 2px 2px rgba(0,0,0,0.3) ;
}
<html>
<body>
<section class="center">
<section class="appletv">
<section class="appletv appletv_layer" data-img="https://source.unsplash.com/random">
<section id="hover">Hover Corners</section>
</section>
<section class="appletv appletv_layer">
<section></section>
<section></section>
</section>
</section>
</section>
</body>
</html>
I have a custom cursor on my site that I want to hide on touch devices (mobile/tablet). I have successfully done this but for a split second when you visit the website the cursor appears in the top left corner then is hidden. Is there any way to stop it displaying at all?
This is the code im using to remove the ID of the cursor on touch devices.
jQuery(document).ready(function($) {
{
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent)) {
$('#custom-cursor').remove();
}
}
});
jQuery(document).ready(function($) {
let cursor = document.querySelector('#custom-cursor');
document.addEventListener('mousemove', evt => {
let { clientX: x, clientY: y } = evt;
let scale = 1;
if (evt.target.matches('a,span,[onclick],img,video,i')) {
cursor.classList.add('active');
scale = 0.5;
} else {
cursor.classList.remove('active');
}
cursor.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
});
});
* {
cursor: none;
}
#custom-cursor {
position: fixed;
width: 20px; height: 20px;
top: -10px;
left: -10px;
border: 2px solid black;
border-radius: 50%;
opacity: 1;
background-color: #fb4d98;
pointer-events: none;
z-index: 99999999;
transition:
transform ease-out 0.15s,
border 0.5s,
opacity 0.5s,
background-color 0.5s;
}
#custom-cursor.active {
opacity: 0.5;
background-color: #000;
border: 2px solid #fb4d98;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="custom-cursor"></div>
Without seeing more of your code it's not possible to be absolutely sure, but from the info in the question it looks as though the whole page is loaded before the cursor is removed.
You could tackle this in a variety of ways, for example not having the cursor element in the initial HTML but adding it if required onload.
Alternatively you could leave your initial HTML as it is, but set the cursor to have display: none in your CSS. Then onload the JS adds setting the style.display to block if the cursor is not to be removed.
UPDATE: now having seen more of the code here is a snippet to show how the second method (cursor to have display: none until the page is loaded) might be implemented:
jQuery(document).ready(function($) {
let cursor = document.querySelector('#custom-cursor');
if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(navigator.userAgent)) {
$('#custom-cursor').remove();
}
else { cursor.style.display = 'block';}
document.addEventListener('mousemove', evt => {
let { clientX: x, clientY: y } = evt;
let scale = 1;
if (evt.target.matches('a,span,[onclick],img,video,i')) {
cursor.classList.add('active');
scale = 0.5;
} else {
cursor.classList.remove('active');
}
cursor.style.transform = `translate(${x}px, ${y}px) scale(${scale})`;
});
});
* {
cursor: none;
}
#custom-cursor {
position: fixed;
width: 20px; height: 20px;
top: -10px;
left: -10px;
border: 2px solid black;
border-radius: 50%;
opacity: 1;
background-color: #fb4d98;
pointer-events: none;
z-index: 99999999;
transition:
transform ease-out 0.15s,
border 0.5s,
opacity 0.5s,
background-color 0.5s;
display: none;
}
#custom-cursor.active {
opacity: 0.5;
background-color: #000;
border: 2px solid #fb4d98;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="custom-cursor"></div>
I've run into issue with Three.js (WebGL) - it freezes background animations (.gif in my case) and CSS transitions? Bascially when i don't trigger any CSS transition, the background GIF seems to freeze... but when I do trigger CSS transition (literally any), everything seems to move/animate/transition like it should. First time seeing this, so I would appreciate any help on debugging this and explaining me why does it act that way!? Should i set up some kind of delta time for animate()?
Here is my code example (try hovering menu, to see the issue with bg):
/**
* Hero Canvas
*/
var heroCanvasWrap = document.getElementById('hero-canvas');
THREE.Cache.enabled = true;
let container;
let camera, scene, renderer;
let group, textMesh1, textGeo, materials;
let text = "three.js",
bevelEnabled = true,
font = undefined;
const height = 20,
size = 70,
curveSegments = 4,
bevelThickness = 2,
bevelSize = 1.5;
const fontMap = {
"helvetiker": 0,
"optimer": 1,
"gentilis": 2,
"droid/droid_sans": 3,
"droid/droid_serif": 4
};
const weightMap = {
"regular": 0,
"bold": 1
};
const reverseFontMap = [];
const reverseWeightMap = [];
for ( const i in fontMap ) reverseFontMap[ fontMap[ i ] ] = i;
for ( const i in weightMap ) reverseWeightMap[ weightMap[ i ] ] = i;
let targetRotation = 0;
let targetRotationOnPointerDown = 0;
let pointerX = 0;
let pointerXOnPointerDown = 0;
let windowHalfX = window.innerWidth / 2;
let fontIndex = 1;
init();
animate();
function decimalToHex( d ) {
let hex = Number( d ).toString( 16 );
hex = "000000".substr( 0, 6 - hex.length ) + hex;
return hex.toUpperCase();
}
function init() {
container = document.createElement( 'div' );
heroCanvasWrap.appendChild( container );
// CAMERA
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1500 );
camera.position.set( 0, 100, 500 );
// cameraTarget = new THREE.Vector3( 0, 150, 0 );
// SCENE
scene = new THREE.Scene();
// LIGHTS
const dirLight = new THREE.DirectionalLight( 0xffffff, 0.75 );
dirLight.position.set( 0, 0, 70 ).normalize();
scene.add( dirLight );
// Get text from hash
materials = [
new THREE.MeshPhongMaterial( { color: 0xE1885D } )
];
group = new THREE.Group();
group.position.y = 100;
scene.add( group );
const loader = new THREE.FontLoader();
loader.load( 'http://localhost:1234/montserrat-bold.json', function ( response ) {
font = response;
refreshText();
} );
// RENDERER
renderer = new THREE.WebGLRenderer( { alpha:true, antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
container.appendChild( renderer.domElement );
// EVENTS
container.style.touchAction = 'none';
}
function createText() {
textGeo = new THREE.TextGeometry( text, {
font: font,
size: size,
height: height,
curveSegments: curveSegments,
bevelThickness: bevelThickness,
bevelSize: bevelSize,
bevelEnabled: bevelEnabled
} );
textGeo.computeBoundingBox();
const centerOffset = - 0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
textMesh1 = new THREE.Mesh( textGeo, materials );
textMesh1.position.x = centerOffset;
group.add( textMesh1 );
}
function refreshText() {
group.remove( textMesh1 );
if ( ! text ) return;
createText();
}
//
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
group.rotation.y += ( targetRotation - group.rotation.y ) * 0.05;
// camera.lookAt( cameraTarget );
renderer.clear();
renderer.render( scene, camera );
}
/**
* Document is ready?
* #param {*} callbackFunc
*/
function ready(callbackFunc) {
if (document.readyState !== 'loading') {
// Document is already ready, call the callback directly
callbackFunc();
} else if (document.addEventListener) {
// All modern browsers to register DOMContentLoaded
document.addEventListener('DOMContentLoaded', callbackFunc);
} else {
// Old IE browsers
document.attachEvent('onreadystatechange', function() {
if (document.readyState === 'complete') {
callbackFunc();
}
});
}
}
/**
* Document is redy! Init js...
*/
ready(function() {
/**
* Toggle light/dark mode
*/
var lightToggle = document.querySelector('.light-toggle'),
theBody = document.body;
lightToggle.onclick = function() {
theBody.classList.toggle('dark');
}
/**
* Toggle main menu
*/
var toggleMenu = document.querySelector('.toggle-menu'),
toggleMenuContent = document.getElementById('toggle-menu-content'),
headerHead = document.querySelector('header.head');
toggleMenu.onclick = function() {
/* theBody.classList.toggle('overflow-hidden'); */
headerHead.classList.toggle('fixed');
toggleMenuContent.classList.toggle('open');
}
});
/** Tailwind **/
#import 'https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind.min.css';
/** Montserrat Font **/
#import url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght#0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
/**
* MAIN
**/
body {
width: 100%;
height: 100%;
font-family: "Montserrat", sans-serif;
overflow-x: hidden;
color: #000000;
background-color: #ffffff;
background-image: url("https://i.ibb.co/mGVpddB/bg.gif");
background-repeat: repeat;
}
a:focus,
button:focus{
outline: none !important; /** Remove all link & button outlines **/
}
/** Fullscreen helper **/
.fullscreen-bg {
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
/* background-attachment: fixed; */
}
.btn {
position: relative;
color: #000000;
font-size: 20px;
font-weight: 900;
font-style: italic;
text-transform: lowercase;
z-index: 10;
}
.btn.btn-slash::before{
display: block;
content: "";
position: absolute;
top: 50%;
left: 10%;
transform: translateY(-50%) skewX(25deg);
background: #DBDBDB;
width: 10px;
height: 45px;
-webkit-transition: all 0.3s ease;
-moz-transition: all 0.3s ease;
-o-transition: all 0.3s ease;
transition: all 0.3s ease;
}
.btn.btn-slash:hover::before{
width: 80%;
}
/* HERO */
.light-toggle {
position: relative;
display: block;
width: 30.422px;
height: 31.898px;
z-index: 300;
}
.light-toggle::before,
.light-toggle::after {
content: "";
display: block;
position: absolute;
width: 30.422px;
height: 31.898px;
top: 50%;
margin-top: -15.949px;
}
.light-toggle::before {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30.422' height='31.898' viewBox='0 0 30.422 31.898'%3E%3Cpath class='a' d='M11.576,54A15.415,15.415,0,0,1,1.269,50.192a12.031,12.031,0,0,1,0-18.385A15.415,15.415,0,0,1,11.576,28a16.274,16.274,0,0,1,3.924.477,14.859,14.859,0,0,0-7.63,4.6,11.906,11.906,0,0,0,0,15.853,14.86,14.86,0,0,0,7.63,4.6A16.273,16.273,0,0,1,11.576,54Z' transform='matrix(0.799, -0.602, 0.602, 0.799, -14.455, -13.034)' style='fill:gray;'/%3E%3C/svg%3E%0A");
}
#toggle-menu-content {
position: relative;
top: 0;
left: 0;
width: 100%;
height: 0;
overflow: hidden;
}
#toggle-menu-content.open {
height: 100% !important;
}
#toggle-menu-content footer .light-toggle{
margin-top: 35px;
}
header.head {
position: absolute;
}
header.head.fixed {
z-index: 300;
}
.menu-links li {
margin-bottom: 30px;
}
.menu-links li a {
-webkit-transition: all 0.3s ease;
-moz-transition: all 0.3s ease;
-o-transition: all 0.3s ease;
transition: all 0.3s ease;
}
.menu-links li a {
font-size: 30px;
font-weight: 600;
}
.menu-links li a:hover {
margin-left: -25px;
padding-right: 25px;
}
.menu-links li.active {
margin-left: -80px !important;
}
.menu-links li.active a:hover {
margin-left: 0 !important;
padding-right: 0 !important;
}
.menu-links li.active a {
color: #E1885D;
font-weight: 800;
font-style: italic;
}
.menu-links li.active a::before {
display: inline-block;
content: "👉";
float: left;
margin-right: 10px;
}
/** Footer **/
footer address a {
font-style: normal !important;
font-weight: 500;
font-size: 18px;
}
.socials li {
margin-bottom: 10px;
}
.socials li a {
display: block;
font-weight: 700;
font-size: 18px;
color: #808080;
-webkit-transform: rotate(-90deg);
-moz-transform: rotate(-90deg);
-ms-transform: rotate(-90deg);
-o-transform: rotate(-90deg);
transform: rotate(-90deg);
}
.socials li a .dot {
color: #FA1DA5;
}
/**
* MAIN / DARK
**/
body.dark {
color: #ffffff !important;
background-color: #000000 !important;
background-image: url("../img/dark-bg.gif") !important;
}
.dark .btn.btn-slash {
color: #ffffff;
}
.dark .btn.btn-slash::before {
background: #4D4D4D;
}
.dark .footer-logo path,
.dark .footer-logo rect {
fill: #ffffff;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/tailwindcss/2.0.3/tailwind.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r125/three.min.js"></script>
<!DOCTYPE html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>EC</title>
<meta name="description" content="" />
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<nav id="toggle-menu-content">
<header class="p-36">
<ul class="menu-links">
<li class="active">īsumā</li>
<li>pratība</li>
<li>paveiktais</li>
<li>klienti & draugi</li>
</ul>
</header>
<footer class="pl-36 pr-12 py-20 grid grid-cols-6 gap-x-24">
<div class="col-span-3"></div>
<div class="relative col-start-5 col-end-7">
<div class="absolute bottom-0 right-0">
<ul class="socials relative inline float-right">
<li>ig<span class="dot">.</span></li>
<li>fb<span class="dot">.</span></li>
</ul>
<button class="light-toggle mr-12"></button>
</div>
</div>
</footer>
</nav>
<header id="hero" class="relative w-full h-full">
<header class="head top-0 left-0 w-full p-12 z-20">
<nav class="menu float-right">
<button class="btn btn-slash hover toggle-menu"><span class="relative z-10">menu</span></button>
</nav>
</header>
<main id="hero-canvas" class="relative clear-both w-full h-full">
</main>
</header>
</body>
</html>
Just in case if it's due to browser support, here is video screenshot how it looks on my end:
<div style="width: 100%; height: 0px; position: relative; padding-bottom: 56.250%;"><iframe src="https://streamable.com/e/npetvt" frameborder="0" width="100%" height="100%" allowfullscreen style="width: 100%; height: 100%; position: absolute;"></iframe></div>
Thank you in advance!
I'm trying to create an image zoom effect similar to this one. I've managed to search a plugin called prefixfree.js and tried it in my code, but it did not work, its just showing the image but when I hover it there is no image zoom effect.
The link for the plugin is this. It should suppose to work like this.
Also for additional info, the size for the large image is 1406X1275 and the small image is 200X200. Kindly help me on solving this one or provide better alternatives.
$(document).ready(function() {
var native_width$ = 0;
var native_height = 0;
$(".magnify").mousemove(function(e) {
//When the user hovers on the image, the script will first calculate
//the native dimensions if they don't exist. Only after the native dimensions
//are available, the script will show the zoomed version.
if (!native_width && !native_height) {
//This will create a new image object with the same image as that in .small
//We cannot directly get the dimensions from .small because of the
//width specified to 200px in the html. To get the actual dimensions we have
//created this image object.
var image_object = new Image();
image_object.src = $(".small").attr("src");
//This code is wrapped in the .load function which is important.
//width and height of the object would return 0 if accessed before
//the image gets loaded.
native_width = image_object.width;
native_height = image_object.height;
} else {
//x/y coordinates of the mouse
//This is the position of .magnify with respect to the document.
var magnify_offset = $(this).offset();
//We will deduct the positions of .magnify from the mouse positions with
//respect to the document to get the mouse positions with respect to the
//container(.magnify)
var mx = e.pageX - magnify_offset.left;
var my = e.pageY - magnify_offset.top;
//Finally the code to fade out the glass if the mouse is outside the container
if (mx < $(this).width() && my < $(this).height( && mx > 0 && my > 0) {
$(".large").fadeIn(100);
} else {
$(".large").fadeOut(100);
}
if ($(".large").is(":visible")) {
//The background position of .large will be changed according to the position
//of the mouse over the .small image. So we will get the ratio of the pixel
//under the mouse pointer with respect to the image and use that to position the
//large image inside the magnifying glass
var rx = Math.round(mx / $(".small").width() * native_width - $(".large").width() / 2) * -1;
var ry = Math.round(my / $(".small").height() * native_height - $(".large").height() / 2) * -1;
var bgp = rx + "px " + ry + "px";
//Time to move the magnifying glass with the mouse
var px = mx - $(".large").width() / 2;
var py = my - $(".large").height() / 2;
//Now the glass moves with the mouse
//The logic is to deduct half of the glass's width and height from the
//mouse coordinates to place it with its center at the mouse coordinates
//If you hover on the image now, you should see the magnifying glass in action
$(".large").css({
left: px,
top: py,
backgroundPosition: bgp
});
}
}
})
})
* {
margin: 0;
padding: 0;
}
.magnify {
width: 200px;
margin: 50px auto;
position: relative;
}
.large {
width: 175px;
height: 175px;
position: absolute;
border-radius: 100%;
/*Multiple box shadows to achieve the glass effect*/
box-shadow: 0 0 0 7px rgba(255, 255, 255, 0.85), 0 0 7px 7px rgba(0, 0, 0, 0.25), inset 0 0 40px 2px rgba(0, 0, 0, 0.25);
/*Lets load up the large image first*/
background: url('microsoftLogo1.jpg') no-repeat;
/*hide the glass by default*/
display: none;
}
#subPic1 {
border: 1px solid black;
margin: 5px;
width: 50px;
height: 50px;
}
#subPic2 {
border: 1px solid black;
margin: 5px;
width: 50px;
height: 50px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div class="magnify">
<div class="large"></div>
<img class="small" src="microsoftLogo1Small.jpg" />
</div>
<script src="http://thecodeplayer.com/uploads/js/prefixfree.js" type="text/javascript"></script>
<img id="subPic1" src="microsoftLogo1.jpg" onclick="getImage1()" /><br/>
<img id="subPic2" src="microsoftLogo2.jpg" onclick="getImage2()" />
HTML
<img src="sample.png" class="zoom" />
CSS
img.zoom {
width: 350px;
height: 200px;
-webkit-transition: all .2s ease-in-out;
-moz-transition: all .2s ease-in-out;
-o-transition: all .2s ease-in-out;
-ms-transition: all .2s ease-in-out;
}
.transition {
-webkit-transform: scale(1.8);
-moz-transform: scale(1.8);
-o-transform: scale(1.8);
transform: scale(1.8);
}
JavaScript
$(document).ready(function(){
$('.zoom').hover(function() {
$(this).addClass('transition');
}, function() {
$(this).removeClass('transition');
});
});
HTML
<div class="item">
<img src="pepsi.jpg" alt="pepsi" width="540" height="548">
<div class="item-overlay top"></div>
</div>
CSS
* {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
margin: 0;
padding: 0;
}
.item {
position: relative;
border: 1px solid #333;
margin: 2%;
overflow: hidden;
width: 540px;
}
.item img {
max-width: 100%;
-moz-transition: all 0.3s;
-webkit-transition: all 0.3s;
transition: all 0.3s;
}
.item:hover img {
-moz-transform: scale(1.1);
-webkit-transform: scale(1.1);
transform: scale(1.1);
}
The question is almost the same as this: how to rotate the shadow effect with CSS?
But my question is a bit more complicated: i use "filter: drop-shadow" because object that i want to have shadow effect is composite - it consists of two primitive figures.
I achieved the desired effect with JS - just rotating the main object and then calculating drop-shadow direction. But the shadow blinks on rerendering, it is visible at least in Chrome.
(function() {
const RAD_TO_DEG = 180/Math.PI,
DEG_TO_RAD = Math.PI/180;
var arrow = document.getElementsByClassName('arrow')[0],
arrow_shadow_color = 'rgba(50,50,50,0.25)',
previous_x = 0,
previous_y = 0,
shadow_angle = -45,
shadow_blur_radius = 5,
shadow_offset = 15,
shadow_string_right = 'px ' + shadow_blur_radius + 'px ' + arrow_shadow_color + ')',
amount_of_attempts_to_skip = 10,
n = 0;
dropShadow(180);
document.addEventListener('mousemove', mouseMove);
function mouseMove(e) {
n++;
if (n%amount_of_attempts_to_skip === 0) {
var angle = Math.atan2( previous_y - e.pageY, e.pageX - previous_x ) * RAD_TO_DEG;
arrow.style.transform = 'rotate(' + (180 - ~~angle) + 'deg)';
dropShadow(angle);
previous_x = e.pageX;
previous_y = e.pageY;
}
}
function dropShadow(angle) {
angle = 180 - shadow_angle + angle;
var x = ( shadow_offset * Math.cos( angle * DEG_TO_RAD) ).toFixed(2),
y = ( shadow_offset * Math.sin( angle * DEG_TO_RAD) ).toFixed(2);
arrow.style.filter = 'drop-shadow(' + x + 'px ' + y + shadow_string_right;
}
})();
html, body {
width: 100%;
height: 100%;
box-sizing: border-box;
}
* {
margin: 0;
border: 0;
padding: 0;
position: relative;
box-sizing: inherit;
}
.container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-100%, -50%);
}
.arrow {
width: 75px;
height: 20px;
background: #2ECC40;
transform-origin: right;
transition: all 0.15s ease;
}
.arrow:before {
content: "";
position: absolute;
border-bottom: 15px solid transparent;
border-right: 20px solid #2ECC40;
border-top: 15px solid transparent;
height: 0px;
width: 0px;
margin-left: -20px;
margin-top: -5px;
}
<div class="container"><div class="arrow"></div></div>
So the question is: is it possible to create a shadow effect for a composite object with CSS and then rotate it so that it keeps the absolute angle with CSS?
Or maybe at least with JS but some other way but manually setting x and y filter offsets.
UPD: i just realized that there is just no need to dynamically apply drop-shadow style - it can be applied to a container: there will be no rerendering flashes, no need to apply some techniques to smoothen the shadow movement, no need to manually calculate shadow offset, that's it. I answered my own question 'cuz it was silly.
I just realized that there is just no need to dynamically apply drop-shadow style - it can be applied to a container: there will be no rerendering flashes, no need to apply some techniques to smoothen the shadow movement, no need to manually calculate shadow offset, that's it. All of these will be rendered automatically.
So the answer for "is it possible to create a shadow effect for a composite object with CSS and then rotate it so that it keeps the absolute angle with CSS?" is Yes, it is possible: just apply drop-shadow filter to the container of the element that you want to have a shadow effect.
Stackoverflow, sorry for asking silly questions.
Shadow blinking is out of bug. I fixed your thing at my CodePen and below. Your project's arrow will get dynamic shadow with only CSS if you create pseudo element which will move with cursor.
That flickering of the shadow of 3D objects upon cursor move is browser specific long known CSS related kind of bug with fixes available everywhere. You only needed to know that matter. You can search StackOverflow and perform web search now. Two ways has minor difference in CSS. But both actually works. I have not changed your javascript.
You can read/see W3C docs, CSS tricks's this, CSS trick's this,W3 School and this code pen for CSS pseudo element drag-able drop shadow.
For your case I modified this :
.arrow {
width: 75px;
height: 20px;
background: #2ECC40;
transform-origin: right;
transition: all 0.01s ease;
transform-style: preserve-3d;
-webkit-transform: rotateY(60deg);
-webkit-transform-style: preserve-3d;
transform: rotateY(60deg);
(function() {
const RAD_TO_DEG = 180/Math.PI,
DEG_TO_RAD = Math.PI/180;
var arrow = document.getElementsByClassName('arrow')[0],
arrow_shadow_color = 'rgba(50,50,50,0.25)',
previous_x = 0,
previous_y = 0,
shadow_angle = -45,
shadow_blur_radius = 5,
shadow_offset = 15,
shadow_string_right = 'px ' + shadow_blur_radius + 'px ' + arrow_shadow_color + ')',
amount_of_attempts_to_skip = 10,
n = 0;
dropShadow(180);
document.addEventListener('mousemove', mouseMove);
function mouseMove(e) {
n++;
if (n%amount_of_attempts_to_skip === 0) {
var angle = Math.atan2( previous_y - e.pageY, e.pageX - previous_x ) * RAD_TO_DEG;
arrow.style.transform = 'rotate(' + (180 - ~~angle) + 'deg)';
dropShadow(angle);
previous_x = e.pageX;
previous_y = e.pageY;
}
}
function dropShadow(angle) {
angle = 180 - shadow_angle + angle;
var x = ( shadow_offset * Math.cos( angle * DEG_TO_RAD) ).toFixed(2),
y = ( shadow_offset * Math.sin( angle * DEG_TO_RAD) ).toFixed(2);
arrow.style.filter = 'drop-shadow(' + x + 'px ' + y + shadow_string_right;
}
})();
html, body {
width: 100%;
height: 100%;
box-sizing: border-box;
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
}
* {
margin: 0;
border: 0;
padding: 0;
position: relative;
box-sizing: inherit;
transform-style: preserve-3d;
}
.container {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-100%, -50%);
transform-style: preserve-3d;
-webkit-transform-style: preserve-3d;
}
.arrow {
width: 75px;
height: 20px;
background: #2ECC40;
transform-origin: right;
transition: all 0.01s ease;
transform-style: preserve-3d;
-webkit-transform: rotateY(60deg);
-webkit-transform-style: preserve-3d;
transform: rotateY(60deg);
}
.arrow:before {
content: "";
position: absolute;
border-bottom: 15px solid transparent;
border-right: 20px solid #2ECC40;
border-top: 15px solid transparent;
height: 0px;
width: 0px;
margin-left: -20px;
margin-top: -5px;
}
<div class="container"><div class="arrow"></div></div>
Depends on what kind of solution you are looking for. If you need a lot of elements with shadows, it's better to use a prerendered image. Browser won't spend time calculating all the shadows and rotations for each element.
If you absolutely need a shadow on a composite object with CSS, use box-shadow. There is a hacky way to make a triangle with the shadow. It's much better and efficient to use an image though!
Here by rotating the wrapper element we rotate all of its children and automatically their box-shadow:
(matrix value is taken from the computed style)
<!doctype html>
<html>
<head>
<style>
#keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.arrow {
top: 150px;
left: 50px;
position: relative;
display: block;
width: 280px;
animation: rotate 5s infinite linear;
}
.arrow div {
display: inline-block;
position: absolute;
}
.arrow-body {
width: 251px;
height: 25px;
top: 16px;
background: green;
box-shadow: 1px 5px 0 0 black;
}
.arrow-head {
width: 0;
height: 0;
right: 0;
bottom: -84px;
box-sizing: border-box;
border: 1em solid black;
border-color: transparent transparent green green;
transform-origin: 0 0;
transform: rotate(225deg);
box-shadow: -5px 1px 0 0 black;
}
#log {
font-family: monospace;
}
</style>
<script>
setInterval(function(){
var a = document.getElementById("arrow");
var l = document.getElementById("log");
l.innerHTML = ".arrow { transform: " + window.getComputedStyle(a, null).getPropertyValue("transform") + " }";
}, 10);
</script>
</head>
<body>
<span id="log"></span>
<div class="arrow" id="arrow">
<div class="arrow-body"></div>
<div class="arrow-head"></div>
</div>
</body>
</html>