Scroll duration for behaviour = smooth in different browsers - firefox

I'm want to use the window.scrollTo function with smooth behaviour.
For example:
window.scrollTo({ top: 0, behavior: 'smooth' })
Parallel I want to change an elements color. For this I could use the scroll event to calculate the color for the current scroll position. But this would result in a bad performance because the scroll callback will called to often.
The better solution would be to start a transition at the same time. But for this I have to know the scroll duration. Since it's not possible to define it manually, I need to know which duration the browsers uses.

The specification says
The scrolling box is scrolled in a smooth fashion
using a user-agent-defined timing function over a user-agent-defined period of time.
User agents should follow platform conventions, if any.
Right away that makes it a complicated question to which there is likely no answer, or at least no answer which will reliably stay put.
Browsers might
Aim for a target speed rather than duration, and so scroll for a longer period of time if there are several pages to scroll
Adjust the scrolling behaviour if many nested scrolling panels are scrolling at once, such as sequencing them one after another (I see some code in Chromium which might be doing something like this)
Allow it to be user-configurable, so people with poor vision or motion sickness can tweak it to their liking, or disable it
Defer to the operating system, which might have its own quirks and customizations
Change any of this with no notice, or the underlying OS might
Here is an excerpt of a comment in some Firefox code to do with smooth scrolling. I did not dig in to whether this is actually strictly relevant to the sorts of scroll you are doing, but it gives an idea:
* |Smooth| scrolls have a symmetrical acceleration and deceleration curve
* modeled with a set of splines that guarantee that the destination will be
* reached over a fixed time interval. |Smooth| will only be smooth if smooth
* scrolling is actually enabled. This behavior is utilized by keyboard and
* mouse wheel scrolling events.
*
* |SmoothMsd| implements a physically based model that approximates the
* behavior of a mass-spring-damper system. |SmoothMsd| scrolls have a
* non-symmetrical acceleration and deceleration curve, can potentially
* overshoot the destination on intermediate frames, and complete over a
* variable time interval. |SmoothMsd| will only be smooth if cssom-view
* smooth-scrolling is enabled.
And here is a little bit of code you can use to test for yourself. In my experimentation I'm seeing the duration varying based on how far it scrolls, both in Firefox and Chromium, and I'm seeing different speeds in each of those.
const qs = document.querySelector.bind(document);
const viewportHeightInput = qs("#viewport-height");
const contentHeightInput = qs("#content-height");
const viewport = qs("#viewport");
const content = qs("#content");
const output = qs("#output");
function update() {
viewport.style.height = `${viewportHeightInput.value}px`;
content.style.height = `${contentHeightInput.value}px`;
}
update();
viewportHeightInput.addEventListener("input", update);
contentHeightInput.addEventListener("input", update);
qs("#to-top").addEventListener("click", () => {
start = performance.now();
scrollEvents = 0;
updateScrollEvents();
viewport.scrollTo({
behavior: "smooth",
top: 0,
})
});
qs("#to-bottom").addEventListener("click", () => {
start = performance.now();
scrollEvents = 0;
updateScrollEvents();
viewport.scrollTo({
behavior: "smooth",
top: viewport.scrollHeight - viewport.clientHeight,
})
});
const scrollEventsOutput = qs("#scroll-events");
const elapsedOutput = qs("#elapsed");
let scrollEvents = 0;
let start = performance.now();
let last = null;
viewport.addEventListener("scroll", () => {
last = performance.now();
scrollEvents++;
updateScrollEvents();
});
function updateScrollEvents() {
scrollEventsOutput.value = scrollEvents;
elapsedOutput.value = last == null ? 0 : last - start;
}
#controls {
display: grid;
grid-template-columns: 10rem 1fr;
}
#controls fieldset {
display: contents;
}
#viewport {
overflow: auto;
border: thick solid orange;
margin: 4rem 0;
}
#content {
background-image: linear-gradient(to bottom left, black, white);
position: relative;
}
#content::before,
#content::after {
position: absolute;
left: 0;
background-color: black;
color: white;
display: block;
}
#content::before {
content: "start";
top: 0;
}
#content::after {
content: "end";
bottom: 0;
}
<div id="controls">
<fieldset>
<label for="viewport-height">Viewport height</label>
<input id="viewport-height" type="number" min="1" value="400">
</fieldset>
<fieldset>
<label for="content-height">Content height</label>
<input id="content-height" type="number" min="1" value="1000">
</fieldset>
<fieldset>
<label>Triggers</label>
<ul>
<li><button id="to-top">Scroll to top</button></li>
<li><button id="to-bottom">Scroll to bottom</button></li>
</ul>
</fieldset>
<fieldset>
<label for="scroll-events">Scroll events</label>
<input id="scroll-events" type="number" readonly value="0">
</fieldset>
<fieldset>
<label for="elapsed">Milliseconds between start of scroll and last scroll event</label>
<input id="elapsed" type="number" readonly value="0">
</fieldset>
</div>
<div id="viewport">
<div id="content"></div>
</div>

The code example I shared in the comment is just for reference. You further need to modify the code as per your own requirement for scrolling the page.
Here, I have tried to modify that sample code to scroll the page.
$(window).scroll(function() {
// selectors
var $window = $(window),
$body = $('body'),
$panel = $('.panel');
// Change 33% earlier than scroll position so colour is there when you arrive.
var scroll = $window.scrollTop() + ($window.height() / 3);
$panel.each(function () {
var $this = $(this);
// if position is within range of this panel.
// So position of (position of top of div <= scroll position) && (position of bottom of div > scroll position).
// Remember we set the scroll to 33% earlier in scroll var.
if ($this.position().top <= scroll && $this.position().top + $this.height() > scroll) {
// Remove all classes on body with color-
$body.removeClass(function (index, css) {
return (css.match (/(^|\s)color-\S+/g) || []).join(' ');
});
// Add class of currently active div
$body.addClass('color-' + $(this).data('color'));
}
});
}).scroll();
$(document).ready(function(){
$("button").click(function(){
$("html, body").animate({
scrollTop: $("#bottom").offset().top - 220
}, 8000);
// document.getElementById('bottom').scrollIntoView({behavior: "smooth"});
});
});
/* Setting fade transition and default settings */
body {
color: #000;
background-color: #f4f4f4;
transition: background-color 1s ease;
}
/* panel styles */
.panel {
/* min height incase content is higher than window height */
min-height: 100vh;
display: flex;
justify-content: space-around;
align-items: center;
font-family: sans-serif;
/* outline: 10px solid hotpink; */
/* turn above on to see the edge of panels */
}
/* colours */
.color-violet {
background-color: #7A4EAB;
}
.color-indigo {
background-color: #4332CF;
}
.color-blue {
background-color: #2F8FED;
}
.color-green {
background-color: #4DCF42;
}
.color-yellow {
background-color: #FAEB33;
}
.color-orange {
background-color: #F19031;
}
.color-red {
background-color: #F2293A;
}
/* styling for demo, can ignore */
body {
text-align: center;
font-size: 120%;
line-height: 1.618;
}
h1, h2 {
font-size: 3em;
letter-spacing: -0.05em;
line-height: 1.1;
}
p {
max-width: 30em;
margin-bottom: 1.618em;
}
a {
color: #4332CF;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<div class="panel" data-color="white">
<div>
<h1>scrolling example</h1>
<div><button>Click me to scroll to bottom</button></div>
</div>
</div>
<div class="panel" data-color="violet">
<h2>Violet panel</h2>
</div>
<div class="panel" data-color="indigo">
<h2>Indigo panel</h2>
</div>
<div class="panel" data-color="blue">
<h2>Blue panel</h2>
</div>
<div class="panel" data-color="green">
<h2>Green panel</h2>
</div>
<div class="panel" data-color="yellow">
<h2>Yellow panel</h2>
</div>
<div class="panel" data-color="orange">
<h2>Orange panel</h2>
</div>
<div class="panel" data-color="red">
<h2>Red panel</h2>
</div>
<div id="bottom"></div>
Output in the MS Edge Chromium browser:
Further, you can try to modify the code sample as per your requirements.
Reference:
Code example link

Related

Scroll hero animation using main scroll bar/scroll jacking

I'm trying to create a scroll animation similar to Apple (which there are a lot of codepen examples and tutorials for that, such as these: https://codepen.io/Maltsbier/pen/dyYmGGq or https://codepen.io/j-v-w/pen/ZEbGzyv). However, what I need is for it to not take over the entire screen, but instead, just a portion of the screen like a typical hero (around 600-700px) before allowing the user to scroll the rest of the page (which would just be regular html). I've sort of simulated it in this codepen (https://codepen.io/kmell/pen/RwQzjGp) using "position: sticky" and overflow: scroll on the parent div, but it only works when you use your mousewheel and scroll over the video itself. Basically, I'm looking for that effect, but when using the main scroll bar. I believe this will require some "scroll jacking" but after reading tons of articles and looking at a bunch of stackoverflow answers, I just can't seem to find anything that works and/or that I can understand/replicate. I'm also having trouble getting the text to scroll with the animation, but I think I can figure that part out. Anyway, any help, guidance or even just a push in the right direction would be greatly appreciated. Happy to provide more context if needed. Thank you!
Here's the HTML:
<div class="container">
<div class="app">
<div id="bound-one" class="scroll-bound">
<div class="content">
<div class="video-holder">
<video width="600" muted="" preload="" id="html5_video_cylzo56m54e">
<source src="https://cdn.ananaspizza.de/file/malte-image-store/v9-webm.webm" type="video/webm">
</video>
</div>
</div>
<div class="text-holder">
<h1>Here is the first bit of text</h1>
<p>Here is the first sub-title</p>
<h1 class="second-scroll">Here is the second bit of text</h1>
<p>Here is the second sub-title</p>
</div>
</div>
</div>
</div>
<div class="spacer">More Content would go here that does not require any of this scrolling functionality, just plain text.</div>
Here's the CSS:
.app { height: 700px; overflow-y: scroll; }
.scroll-bound { height:300vh; }
.scroll-bound .content { height: 700px; width: 50%; position: sticky; position: -webkit-sticky; top:0; }
.scroll-bound video { width: 100%; }
.second-scroll { margin-top: 115vh;}
/*.app::-webkit-scrollbar { display: none; }*/
.text-holder { width: 50%; position: absolute; top: 70px; right: 0; }
.spacer { min-height: 500px; background: #ccc; }
Here's the JS:
const registerVideo = (bound, video) => {
bound = document.querySelector(bound);
video = document.querySelector(video);
const scrollVideo = ()=>{
if(video.duration) {
const distanceFromTop = window.scrollY + bound.getBoundingClientRect().top;
const rawPercentScrolled = (window.scrollY - distanceFromTop) / (bound.scrollHeight - window.innerHeight);
const percentScrolled = Math.min(Math.max(rawPercentScrolled, 0), 1);
video.currentTime = video.duration * percentScrolled;
}
requestAnimationFrame(scrollVideo);
}
requestAnimationFrame(scrollVideo);
}
registerVideo("#bound-one", "#bound-one video");

How to make rotated <hr> line length adapt to page height?

I have a rotated hr line. See far right here.
Is it possible to make the line's length adapt to the height of the page?
Thank you!
Here is a script that calculates letter-spacing:
#container {
width: 50px;
height: 500px;
display: block;
border: 1px solid red;
}
p {
writing-mode: vertical-rl;
text-orientation: mixed;
}
<div id="container">
<p>My Text Here</p>
<script>
const container = document.querySelector('#container');
const text = container.querySelector('p');
const height0 = text.offsetHeight;
text.style.letterSpacing = '1px';
const height1 = text.offsetHeight;
const spacing = (container.offsetHeight - height0 - 2) / (height1 - height0);
text.style.letterSpacing = `${spacing}px`;
</script>
</div>
Tried also text-align: justify with text-justify and it seems to be incompatible with vertically oriented texts...

Kendo UI MVVM with Kendo UI Drag and Drop

I am attempting to use the Kendo UI MVVM framework with the Kendo UI drag and drop mechanic; But I am having a very difficult time finding out how to get the data dropped out of the draggable object.
My code is something like this ...
var viewModel = kendo.observable {
Cart : [],
Items : [
{
Id : "item/10",
Name: "CD ROM"
},
{
Id : "item/11",
Name: "DVD ROM"
}
};
So then I have a rough template binding...
<script id="products-template" type="text/x-kendo-template">
<li class="draggable">
<div data-bind="text: Name"></div>
</li>
</script>
Then this gets called up in a list...
<div id="shopping-items-available">
<ul data-template="products-template" data-bind="source: Items">
</ul>
</div>
Then there is a standard "drop target" (taken from the kendo docs)
<div id="droptarget">Start dragging.</div>
with the following CSS
#droptarget {
border: 1px solid #959595;
height: 198px;
width: 300px;
font-size: 36px;
border-radius: 37px;
text-align: center;
line-height: 198px;
color: #a1a1a1;
text-shadow: 0 1px 1px #fff;
margin: 0 0 30px 220px;
cursor: default;
background: #dddddd;
background: -moz-linear-gradient(top, #dddddd 0%, #c1c1c1 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#dddddd), color-stop(100%,#c1c1c1));
background: -webkit-linear-gradient(top, #dddddd 0%,#c1c1c1 100%);
background: -o-linear-gradient(top, #dddddd 0%,#c1c1c1 100%);
background: -ms-linear-gradient(top, #dddddd 0%,#c1c1c1 100%);
background: linear-gradient(top, #dddddd 0%,#c1c1c1 100%);
}
Now in the javascript, I turn the shopping-items-available div into a draggable.
$("body").kendoDraggable({
hint: function (target) {
return $(target).clone().addClass("draggable");
},
filter: ".draggable"
});
and lastly, I initialize the drop target.
$("#droptarget").kendoDropTarget({
drop: droptargetOnDrop
});
but in my code, I cannot seem to get the actual data about the item that was dropped.
function droptargetOnDrop(e) {
console.log(e);
$("#droptarget").text("You did great!");
$(".draggable").removeClass("hollow");
}
The data of the item being dropped is received by droptargetOnDrop as e.draggable.currentTarget.
If you want to move the item to the target area, you should do something like:
$("#droptarget").append(e.draggable.currentTarget);
NOTE Doing this, you will be moving the item. If you want to append a copy, you should append a clone of the node:
$("#droptarget").append($(e.draggable.currentTarget).clone());
EDIT: For getting the data item without using a kendoDataSource I propose to change the template to:
<script id="products-template" type="text/x-kendo-template">
<li class="draggable" data-id="${Id}">
<div data-bind="text: Name"></div>
</li>
</script>
This saves the Id (or any information that allows you to find the element latter) in the DOM being dragged. Then in the handler we retrieve the Id and search the item corresponding to that Id.
function droptargetOnDrop(e) {
var dom = e.draggable.currentTarget;
var id = $(dom).data("id");
var items = viewModel.Items;
for (var i = 0; i < items.length; i++) {
if (items[i].Id == id) {
alert("Found : " + JSON.stringify(items[i]));
break;
}
}
}
EDIT If you decide to use kendoListView you should define your template as:
<script id="products-template" type="text/x-kendo-template">
<li class="draggable">
<div>${Name}</div>
</li>
</script>
The HTML container for the list would be:
Then initialize the ListView as:
var list = $("#shopping-items-available > ul").kendoListView({
template : $("#products-template").html(),
dataSource: viewModel.Items
}).data("kendoListView");
and finally the droptargetOnDrop function:
function droptargetOnDrop(e) {
var dom = e.draggable.currentTarget;
var item = list.dataSource.getByUid(dom.data("uid"));
alert("Found : " + JSON.stringify(item));
}

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>

Customizing scrollable plugin with prevpage and nextpage over the image? (see mock up)

I am implementing a scrollable for a portfolio gallery.
(scrollable = scrollable plugin from http://flowplayer.org/tools/index.html )
There will be one item visible at a time.
By default, scrollable positions the prev/next buttons outside of the image area and clicking on the current image advances the scrollable content.
I would like to have the prev/next render within the image area.
I would like to have an image caption appear when mousing over the lower part of the image.
Mock-up:
http://i303.photobucket.com/albums/nn160/upstagephoto/mockups/scrollable_mockup.jpg
Any ideas on how to achieve one or both of these?
Thank you!
The main part of your approach will be like this in your html:
<div id="mainContainer">
<div class="scrollable">
<div class="items">
<div class="scrollableEl">
<img src="yourimage.jpg" />
<div class="caption">Your caption</div>
</div>
<div class="scrollableEl">
<img src="yourimage2.jpg" />
<div class="caption">Your caption 2</div>
</div>
... so on ...
</div>
</div>
«
«
</div>
And like so in your CSS:
.scrollable {
position:relative;
overflow:hidden;
width: 660px;
height:90px;
}
.scrollable .items {
width:20000em;
position:absolute;
}
.items .scrollableEl {
float:left;
positon: relative;
}
.items .scrollableEl .caption {
display:none;
position: absolute;
bottom: 0;
height: 100px;
width: 660px;
}
.items .scrollableEl:hover .caption { /*this will show your caption on mouse over */
display:none;
}
.next, .prev {
position: absolute;
top: 0;
display: block;
width: 30px;
height: 100%;
}
.next {
right: 0;
}
.prev {
left: 0;
}
#mainContainer {
position: relative;
}
The javascript should be fairly standard. Hope this helps!
DEMO: http://jsbin.com/ijede/2 SOURCE: http://jsbin.com/ijede/2/edit
$(function() {
// 5 minute slide show ;-)
$('.next,.prev').click(function(e) {
e.preventDefault();
var pos = parseInt($('.current').attr('id').split('_')[1]);
var tot = $('.slides a').size() - 1;
var click = this.className;
var new_pos = (click == 'next') ? pos + 1: pos - 1;
var slide = ( click == 'next') ?
(pos < tot ? true : false) : (pos > 0 ? true : false);
if (slide) $('.current').toggle(500,function() {
$(this).removeClass('current');
});
$('#pos_' + new_pos).toggle(500,function() {
$(this).attr('class', 'current');
});
});
//cross-browser div :hover
$('.next,.prev').hover(function() {
$(this).children().children().fadeIn(500);
},function() {
$(this).children().children().fadeOut(500);
});
//auto add unique id to each image
$('.slides a').each(function(e) {
$(this).attr('id', 'pos_' + e);
if (!e) $(this).attr('class', 'current');
});
});​
CSS on source!
NOTE: since read the plugin doc require more time for me than make a slideshow from scratch, i have maked a fresh one!
hope you like it!

Resources