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");
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...
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));
}
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>
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!