Bubbling a CSS animation event - events

So according to MDN (it makes sense) the AnimationEvent has the bubble argument but how can one set it to true? Considering the event is fired from a CSS animation.

Ok, it turns out that CSS does bubble events, for example:
HTML:
<ul id="ul">
<li>
<a id="a">Some text!</a>
</li>
</ul>
CSS:
a
{
color: red;
}
a:hover
{
-webkit-animation-duration: 1s;
-webkit-animation-name: change-color;
}
#-webkit-keyframes change-color {
from
{
color: red;
}
to
{
color: blue;
}
}
JS:
var a = document.getElementById( 'a' );
var ul = document.getElementById( 'ul' );
a.addEventListener( 'webkitAnimationEnd', handleEnd );
ul.addEventListener( 'webkitAnimationEnd', handleEnd );
function handleEnd(e)
{
console.log(e);
}
You'll see two events fired. The problem I had was that I was using jQuery's bind() and this binds to a specific selector rather than listening to bubbled events (I think).

Related

Gap right of the page (overflow-x:hidden issue)

There is a gap on the right side of my page probably 10px. I could fix it using oveflow-x:hidden for the html and body but if I do this, it disable the navbar effect
<script>
$(window).scroll(function() {
var scroll = $(window).scrollTop();
if (scroll >= 120) {
$("#mainNav").addClass("scrolling");
} else {
$("#mainNav").removeClass("scrolling");
}
});
</script>
That changes the navbar background-color when scroll down.
I tried width:100% as well, but it didn't work neither.
Could someone help me to eliminate the gap without affect this function?
Thank you
Got it. See http://jsfiddle.net/uffo6you/3/ The CSS specificity ranking of .scrolling wasn't strong enough to override #mainNav. Width and overflow shouldn't have any bearing on this at all. Pls confirm?
<style>
#mainNav{
height: 20vh;
width: 80%;
background-color: blue;
color: white;
position: fixed;
}
#mainContent{
height: 200vh;
padding-top: 20vh;
}
.scrolling{
background-color: red !important;
}
</style>
<div id="mainNav">Top Navbar</div>
<div id="mainContent">
Content goes here
</div>
<script>
$(window).scroll(function() {
var scroll = $(window).scrollTop();
console.log(scroll);
if (scroll >= 80) {
$("#mainNav").addClass("scrolling");
} else {
$("#mainNav").removeClass("scrolling");
}
});
</script>

iOS8 webkit-overflow-scrolling:touch with overlay

I currently have an app with UIWebView that has a scrollable div with the webkit-overflow-scrolling:touch property.
When the side menu is open, I place an overlay (another div) on top of the content to give a dimming effect.
The problem is that when the menu is open (and the overlay in place) when the user pans, the scrollable div actually scrolls when the overlay should be stopping this form happening.
Now, in iOS7, the solution was to add the webkit-overflow-scrolling:touch; to the overlay. That works like a charm, but in iOS8 it doesn't.
Here is a link to an example of the problem. If run on iOS 7 it works as expected, if run on iOS 8 the content in the back will scroll.
.scrollable {
width:100%;
height:200px;
-webkit-overflow-scrolling:touch;
overflow:scroll;
}
.overlay {
position:absolute;
width:100%;
overflow:scroll;
height:200px;
-webkit-overflow-scrolling:touch;
background-color:black;
opacity:.5;
transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
z-index:10;
}
https://jsfiddle.net/SergioM/57f2da87/9/
I also tried setting the overflow-x property of the scrollable div to hidden/auto when the menu is opened but that adds a terrible flicker.
Any suggestion will be much appreciated.
Thanks.
Well after trying many different options I came up with a work around that is good enough for now.
I added a div inside the overlay that is vertically 1% bigger. That now warranties that the event is handled by the overlay and not passed on to the container at the back.
This also allows me to listen to events natively such as pan (horizontally), the vertical ones wont come up but that is ok for now.
.overlayInner {
color:red;
height:101%;
margin-left:30px;
}
here is a link to the fiddle.The margin is unnecessary, just to avoid the number to overlap.
http://jsfiddle.net/SergioM/57f2da87/15/
i don't think this is possible by only using css in the current safari/webkit-version of iOS8.
But you should be able to prevent the scrolling with javascript.
$( ".showHide" ).click( function() {
$( ".overlay" ).toggle();
});
$( ".overlay" ).on( 'touchstart touchmove', function( event ) {
if ( $( this ).is( ":visible" ) ) {
event.preventDefault();
}
});
Update:
i played a little around and came up with another possible solution that could be helpful for you.
HTML:
<button class="showHide">show/hide overlay</button>
<br/>
<br/>
<div class="scrollable">
<div class="overlay"></div>
1
<br/>2
<br/>3
<br/>4
<br/>5
<br/>6
<br/>7
<br/>8
<br/>9
<br/>10
<br/>11
<br/>12
<br/>13
<br/>14
<br/>15
<br/>16
<br/>17
</div>
CSS:
.scrollable {
width:100%;
height:200px;
-webkit-overflow-scrolling:touch;
overflow:scroll;
position: relative
}
.overlay {
content: ' ';
position:absolute;
top: 0;
left: 0;
width:100%;
height:100%;
background-color:black;
opacity:.5;
transform: translate3d(0,0,0);
-webkit-transform: translate3d(0,0,0);
transition: opacity 300ms ease;
z-index:10;
}
Javascript:
$( ".showHide" ).click( function () {
$( ".overlay" ).toggle( { duration: 0, complete: function() {
if ( $( ".overlay" ).is( ":visible" ) ) {
$( ".overlay" ).css( 'top', $( ".scrollable" ).scrollTop() );
$( ".scrollable" ).css( 'overflow', 'hidden' );
} else {
$( ".scrollable" ).css( 'overflow', 'scroll' );
}
}});
});
Example: JSFiddle

Cycle2 Plugin Hide/Show buttons jQuery

I have used the jQuery cycle2 plugin on a blog website. What i want the slideshow to do is when there is more than 1 image displayed, the controls will show-at the moment they are hidden through css.
The plugin is used by declaring it in my header (here is the link to the js file:
http://malsup.github.com/jquery.cycle2.js
And then my css contains a class for the container of the slideshow, class for the container for the buttons and a class each for prev and next buttons:
css:
.cycle-slideshow {
height:400px;
z-index:0;
}
.cycle-slideshow img{
width:100%;
height:100%;
z-index:3;
}
.center {
display:none;
}
.center a{
z-index:4;
position:absolute;
margin-top:-48px;
}
.center a:hover {
display:block;
}
.center a#prev, .center a#next{
position:relative;
width:4%;
height:60px;
width:60px;
margin-top:-60px;
font-size:40px;
text-align:center;
color:#FFF;
}
.center a#next{
float:right;
background:url(images/next.png) center -2px no-repeat;
}
.center a#prev {
float:left;
background:url(images/prev.png) center -2px no-repeat;
}
My html code is actually embedded within a wordpress function but the html format goes along the lines of:
<div class="cycle-slideshow"
data-cycle-fx="scrollHorz"
data-cycle-pause-on-hover="true"
data-cycle-speed="200"
data-cycle-next="#next"
data-cycle-prev="#prev"
data-cycle-swipe="true">
//does stuff here
</div>
<div class="center">
</div>
The following code i was told to do (the first three lines) but this still doesn't seem to work.
$(document).ready(function () {
$('.cycle-slideshow').on( 'cycle-initialized', function( e, opts ) {
if ( opts.slideCount > 1 ) {
$(".center").css("display", "block");
}
});
});
Im not the best with jQuery so can anyone help or give me any guidance please?
In this case you are going to want to initialize the slideshow via code AFTER you add the event handler. Remove the cycle-slideshow class so it doesn't auto initialize and then you can do this:
Demo: http://jsfiddle.net/lucuma/zyhrK/1/
$('.slideshow').on('cycle-initialized', function (e, opts) {
if (opts.slideCount > 1) {
$(".center").css({
"display": "block"
});
}
});
$('.slideshow').cycle();

Making a div draggable to a new position and stay there

This is a newbie question about Kendo UI draggable. I tried to look at their examples but cant really get it.
I want to make a div draggable to another position. When setting this up I can drag the div, but it disappears when released, I want it to stay in the new place. I have tried this but it doesnt work.
$('.draggable').kendoDraggable({
axis: "x",
hint: Hint,
dragstart: DragStart,
drag: Drag,
dragend: DragEnd
});
function Hint (element) {
console.log("hint");
return element;
}
function DragStart(){
console.log("dragstart");
}
function Drag(){
console.log("draging");
}
function DragEnd(event) {
console.log("dragend");
console.log(event.x.location);
$('.draggable').css({'left': event.x.location});
}
I think this is what you want, and I made a Demo for it.
$('.draggable').kendoDraggable({
hint : function (original) {
return original.clone().addClass("ob-clone");
},
dragstart: function (e) {
$(e.target).addClass("ob-hide");
}
});
$('body').kendoDropTarget({
drop: function (e) {
var pos = $(".ob-clone").offset();
$(e.draggable.currentTarget)
.removeClass("ob-hide")
.offset(pos);
}
})
.draggable {
position: absolute;
background: red;
width: 100px;
height: 100px;
vertical-align: middle;
}
.ob-hide {
display: none;
}
.ob-clone {
background: #cccccc;
}
<link href="http://cdn.kendostatic.com/2013.1.319/styles/kendo.common.min.css" rel="stylesheet"/>
<link href="http://cdn.kendostatic.com/2013.1.319/styles/kendo.default.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="http://cdn.kendostatic.com/2013.1.319/js/kendo.all.min.js"></script>
<div id="drop" style="position: absolute; width: 100%; height: 100%; border: 2px solid #000000">
<div class="draggable">
Drag 1
</div>
<div class="draggable">
Drag 2
</div>
</div>
jsFiddle: http://jsfiddle.net/Wayou/fjrcw/

How To Sync CSS Animations Across Multiple Elements?

I have two elements on a page that I'd like to animate via CSS (specifically, -webkit-animation). The animation itself simply bounces an element up and down. One element is always shown and bouncing, the other is not animated until mouse-over (hover).
My question is: Is it possible to sync (have both elements reach their apex at the same time, etc) the animation across both elements regardless of when the 2nd element's animation is started?
Here's my HTML:
<div id="bouncy01">Drip</div>
<div id="bouncy02">droP</div>
and my CSS:
#-webkit-keyframes bounce {
0% {-webkit-transform: translateY(0px);}
25% {-webkit-transform: translateY(-2px);}
50% {-webkit-transform: translateY(-4px);}
75% {-webkit-transform: translateY(-2px);}
100% {-webkit-transform: translateY(0px);}
}
#bouncy01,
#bouncy02 {
margin:10px;
float: left;
background: #ff0000;
padding: 10px;
border: 1px solid #777;
}
#bouncy01 {
-webkit-animation:bounce 0.25s ease-in-out infinite alternate;
}
#bouncy02 {
background: #ffff00;
}
#bouncy02:hover {
-webkit-animation:bounce 0.25s ease-in-out infinite alternate;
}
and finally, a working demo of the issue: http://jsfiddle.net/7ZLmq/2/
(to see the problem, mouse-over the yellow block)
I don't think its possible natively, but you can actually hack similar functionality by using a bouncing wrapper and some position altering
html:
<div id="bouncywrap">
<div id="bouncy01">Drip</div>
<div id="bouncy02">droP</div>
<div>
CSS:
#-webkit-keyframes bounce {
0% { padding-top:1px;}
/* using padding as it does not affect position:relative of sublinks
* using 0 instead of 0 b/c of a box-model issue,
* on kids wiht margin, but parents without margin, just try out
*/
50% { padding-top:5px;} /*desired value +1*/
100% { padding-top:1px;}
}
#bouncy01,
#bouncy02 {
margin:10px;
background: #ff0000;
padding: 10px;
border: 1px solid #777;
width:30px;
position:absolute;
}
#bouncywrap {
-webkit-animation:bounce 0.125s ease-in-out infinite;
position:relative;
width:140px;
height:50px;
/* background:grey; /*debug*/
}
#bouncy02 {
background: #ffff00;
left:60px;
top:2px; /*half of desired value, just a fix for the optic*/
}
#bouncy02:hover {
position:relative; /*here happens the magic*/
top:0px;
}
demo http://jsfiddle.net/A92pU/1/
The Web Animations API now allows to control animations very precisely and quite easily.
There are various ways to declare a Web Animation, but since here we started with CSS, here is how to hook to it:
// when the animation starts
document.querySelector("#bouncy02")
.addEventListener("animationstart", (evt) => {
// double check it the animation we wanted
if (evt.animationName === "bounce") {
// retrieve both Animation objects
const myAnim = findAnimByName(evt.target, "bounce");
const otherAnim = findAnimByName(document.querySelector("#bouncy01"), "bounce");
// update mine to act as if it started
// at the same time as the first one
myAnim.startTime = otherAnim.startTime;
}
});
// simple helper to find an Animation by animationName
function findAnimByName(elem, name) {
// get all the active animations on this element
const anims = elem.getAnimations();
// return the first one with the expected animationName
return anims.find((anim) => anim.animationName === name);
}
#keyframes bounce {
0% {transform: translateY(0px);}
25% {transform: translateY(-2px);}
50% {transform: translateY(-4px);}
75% {transform: translateY(-2px);}
100% {transform: translateY(0px);}
}
#bouncy01,
#bouncy02 {
margin:10px;
float: left;
background: #ff0000;
padding: 10px;
border: 1px solid #777;
}
#bouncy01 {
animation:bounce 0.25s ease-in-out infinite alternate;
}
#bouncy02 {
background: #ffff00;
}
#bouncy02:hover {
animation:bounce 0.25s ease-in-out infinite alternate;
}
<div id="bouncy01">Drip</div>
<div id="bouncy02">droP</div>
Note that while it's surprisingly not yet that propular, this API actually exists for some times now, and its browser support (all except IE) is quite good.
I was looking for an alternative solution to those proposed here because:
I am animating a background color - which can't use the positioning magic in the accepted answer.
I wanted to avoid calculations for a very simple animation in my app.
After further research I came across this module by bealearts.
It exposes a very neat API that lets you keep an animation in sync across the app by referring to it's name:
import sync from 'css-animation-sync';
sync('spinner');
Since this seemed a little too good to be true, I tested the library (which is a single short file) in this fiddle and am happy to report it works (hover on the third image and see that I quickly syncs to the second image's animation) :).
Credit: I used the animation from this fiddle by Simurai as a basis for my fiddle.
If anyone wants to replicate the mechanism behind this synchronisation, the code is open, but in it's essence, it uses events listeners for the animation itself as sync points:
window.addEventListener('animationstart', animationStart, true);
window.addEventListener('animationiteration', animationIteration, true);
Hope this helps the next person looking for a solution to this problem.
Calculate and add a delay before adding the class:
function getTime (seconds) {
const msDuration = (seconds * 1000).toFixed(0);
const currentTime = (new Date()).getTime();
const msDelay = msDuration - (currentTime % msDuration);
return (msDelay / 1000).toFixed(4);
}
$('div').css({animationDelay: getTime(0.6) + "s"}).addClass('animating');
https://codepen.io/s-flhti/pen/GRoVXZw
Looks like you can just stack two of the yellow ones and switch visibility on :hover through a parent element.
You need the animation to always be running otherwise you'll run into the sync issue you've got.
I modified your code a bit to get this.
You could use a setInterval to maintain the animation state of the first animation and give the other animation a negative delay to seek to its matching keyframe on mouse-over.
Read about the state-maintaining-interval-thing here, at the "Manipulating CSS Animations" section; read about the negative delay to seek here.
This was my little quest in synchronizing animations for different elements and pseudo-elements, thanks to the ideas above, the solution turned out to be very simple. I hope this small code helps someone.
window.addEventListener('animationstart', e =>
e.animationName == 'rgb' && e.target.getAnimations({subtree: true}).forEach(e => e.startTime = 0), true)
on mouse hover:
remove animation classes from both elements
use requestAnimationFrame(() => { ... add here "bounce" class to both elements })
Should sync nicely.
You could set a class on the root element which set the altertating state, and then alternate the class using a timer
CSS
.alt .bouncy {
padding-top:5px !important;
}
.bouncy {
padding-top: 1px;
transition: padding-top ease 500ms;
}
HTML
<div class="container">
<div class="bouncy">Drip</div>
<div class="bouncy">droP</div>
<div>
Javascript
$(function () {
setInterval(() => $(".container").toggleClass("alt"), 1000)
})
In this way transition and timer do the work of css animation, but controlled by a single master switch (the container).
With css animation sync lib by bealearts, you can easly synchonize animations. But in version 0.4.1 (lastest today), it had the bugs:
Sync gets lost, when all the sync-animation elements are stopped they animation (for example display:none )
First animation starts from non-zero frame after restarting, that may be critical.
Some time after the start of the first animation, it flashes.
Not working with pseudo-elements :before, :after
To fix all theese bugs (but 4) , you can fix library code:
-Add animation-cancel callback
function animationCancel(event) {
if (shouldSync(event)) {
elements.delete(event.target);
}
}
window.addEventListener('animationcancel', animationCancel, true);
-Modify animation-start callback to process first animation
function animationStart(event) {
if (shouldSync(event)) {
const { target: element, timeStamp } = event;
elements.add(element);
let diff;
if (elements.size == 1){
diff = 0;
lastIterationTimestamp = timeStamp;
}else diff = timeStamp - lastIterationTimestamp;
element.style.setProperty('animation-delay', `-${diff}ms`);
}
}
-And empty the body of init() method.
Here is the fixed using sample:
//Sample demo code
jQuery(function($){
window.cssAnimationSync('pulse-visible');
let animateGroup = function(selector){
let hideNext = function(){
let next = $(selector + ':visible:first');
if (next.length){
next.fadeOut();
setTimeout(hideNext, 200 + Math.random()*200);
}else setTimeout(showNext, 200 + Math.random()*200);
}
let showNext = function(){
let next = $(selector + ':hidden:first');
if (next.length){
next.fadeIn();
setTimeout(showNext, 200 + Math.random()*200);
}else setTimeout(hideNext, 200 + Math.random()*200);
};
showNext();
};
animateGroup('.pulsar_sync');
animateGroup('.pulsar');
});
//Fixed library code
/** #see https://github.com/bealearts/css-animation-sync */
window.cssAnimationSync = function(animationNameOrNames) {
const animationNames = new Set(
Array.isArray(animationNameOrNames) ? animationNameOrNames : [animationNameOrNames]
);
const elements = new Set();
let animationDuration;
let isPaused = false;
let lastIterationTimestamp = 0;
const api = {
getElements() {
return elements;
},
free() {
window.removeEventListener('animationiteration', animationIteration, true);
window.removeEventListener('animationstart', animationStart, true);
this.start();
elements.clear();
},
start() {
elements.forEach((el) => {
if (validate(el)) {
if (isPaused) {
el.style.removeProperty('animation-play-state');
} else {
el.style.removeProperty('animation');
}
}
});
isPaused = false;
},
stop() {
isPaused = false;
elements.forEach((el) => {
if (validate(el)) {
el.style.setProperty('animation', 'none');
}
});
},
pause() {
isPaused = true;
elements.forEach((el) => {
if (validate(el)) {
el.style.setProperty('animation-play-state', 'paused');
}
});
}
};
function shouldSync(event) {
return animationNames.has(event.animationName);
}
function validate(el) {
const isValid = document.body.contains(el);
if (!isValid) {
elements.delete(el);
}
return isValid;
}
function init() {
//setTimeout(restart, animationDuration);
}
function restart() {
api.stop();
setTimeout(api.start, 50);
}
function animationStart(event) {
if (shouldSync(event)) {
const { target: element, timeStamp } = event;
elements.add(element);
let diff;
if (elements.size == 1){
diff = 0;
lastIterationTimestamp = timeStamp;
}else diff = timeStamp - lastIterationTimestamp;
element.style.setProperty('animation-delay', `-${diff}ms`);
}
}
function cssToMs(time) {
const num = parseFloat(time);
let unit = time.match(/m?s/);
if (!unit) return 0;
[unit] = unit;
switch (unit) {
case 's':
return num * 1000;
case 'ms':
return num;
default:
return 0;
}
}
function animationIteration(event) {
if (shouldSync(event)) {
const { target: element, timeStamp } = event;
elements.add(element);
lastIterationTimestamp = timeStamp;
if (!animationDuration) {
animationDuration = cssToMs(window.getComputedStyle(element).animationDuration);
init();
}
}
}
function animationCancel(event) {
if (shouldSync(event)) {
elements.delete(event.target);
}
}
window.addEventListener('animationiteration', animationIteration, true);
window.addEventListener('animationstart', animationStart, true);
window.addEventListener('animationcancel', animationCancel, true);
return api;
};
#keyframes pulse-visible {
0% { opacity: 0.85;}
30% { opacity: 0.85;}
40% { opacity: 0.55;}
45% { opacity: 0;}
85% { opacity: 0;}
90% { opacity: 0.55;}
100% { opacity: 0.85;}
}
#keyframes pulse-visible-copy {
0% { opacity: 0.85;}
30% { opacity: 0.85;}
40% { opacity: 0.55;}
45% { opacity: 0;}
85% { opacity: 0;}
90% { opacity: 0.55;}
100% { opacity: 0.85;}
}
.pulsar
{
animation-name: pulse-visible-copy;
}
.pulsar_sync
{
animation-name: pulse-visible;
}
.pulsar, .pulsar_sync
{
animation-duration: 0.7s;
animation-iteration-count: infinite;
animation-timing-function: linear;
/*styles not depending on animation*/
display: inline-block;
width: 30px;
height: 30px;
margin: 5px;
border: 3px solid red;
border-radius: 25%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
With cssAnimationSync
<div style='min-height:50px;'>
<div class="pulsar_sync" style="display: none;"></div>
<div class="pulsar_sync" style="display: none;"></div>
<div class="pulsar_sync" style="display: none;"></div>
<div class="pulsar_sync" style="display: none;"></div>
<div class="pulsar_sync" style="display: none;"></div>
</div>
Without
<div>
<div class="pulsar" style="display: none;"></div>
<div class="pulsar" style="display: none;"></div>
<div class="pulsar" style="display: none;"></div>
<div class="pulsar" style="display: none;"></div>
<div class="pulsar" style="display: none;"></div>
</div>

Resources