I have some divs that the user can click on, and then they expand to cover the screen. In doing so, they become fixed position.
Everything works exactly as expected in all browsers except for Safari. When on Safari, once the div is expanded, the user cannot click anything inside the div. I have buttons and links that would take the user to other pages or close the div, but it behaves as if the user never clicked anything.
Here is a link: https://toucan-sam.firebaseapp.com/
Any insight would be appreciated.
I've created a snippet below using what I believe to be the relevant code, but the snippet actually displays the intended behavior.
let $body = $('body');
let $introduction = $('#introduction');
let $sidebar = $('#sidebar');
let griffen = {
viewportHeight: $introduction.height(),
viewportWidth: $introduction.width(),
preset: false,
extrasVisible: false,
projectVisible: false,
projectOpening: false,
projectClosing: false,
mobileSize: 900,
};
let openProject = (project) => {
'use strict';
let open = () => {
let containerPosition = project.article.offset().top - window.scrollY;
let containerWidth = project.article.width();
let containerHeight = project.article.height();
project.container.css({
'height': containerHeight + 'px',
'position': 'fixed',
'top': containerPosition + 'px',
'width': containerWidth + 'px',
'z-index': '100',
});
if (project.id === 'sdot') {
project.heading.css({
'background-position': 'center 25%',
});
project.header.animate({
'padding-left': '0',
'padding-top': '20%',
}, 1000);
} else {
project.header.animate({
'padding-left': '0',
}, 1000);
}
project.container.animate({
'height': '100vh',
'top': '0',
'width': '100%',
}, 1000, () => {
project.container.addClass('full');
griffen.projectOpening = false;
});
};
$body.addClass('stuck');
$('html').addClass('stuck');
project.tabButton.attr('aria-selected', 'true');
project.content.attr('aria-hidden', 'false');
let verticalPosition = project.article.offset().top - (griffen.viewportHeight / 2) + (project.article.height() / 2);
let diff = Math.round(verticalPosition) - window.scrollY;
console.log(diff);
if (Math.abs(diff) > 50) {
let doneScrolling = false;
$('html, body').animate({
'scrollTop': verticalPosition
}, 500, () => {
if (!doneScrolling) {
doneScrolling = true;
window.setTimeout(() => {
open();
}, 500);
}
});
} else {
open();
}
};
let closeProject = (project) => {
'use strict';
let close = () => {
let containerPosition = project.article.offset().top - window.scrollY;
let containerWidth = project.article.width();
let containerHeight = project.article.height();
project.container.removeClass('full');
project.container.animate({
'height': containerHeight + 'px',
'top': containerPosition + 'px',
'width': containerWidth + 'px',
}, 1000, () => {
project.container.css({
'height': '',
'position': '',
'top': '',
'width': '',
'z-index': '',
});
$body.removeClass('stuck');
$('html').removeClass('stuck');
griffen.projectClosing = false;
griffen.projectVisible = false;
});
if (project.id === 'sdot') {
project.heading.css({
'background-position': '',
});
if (griffen.viewportWidth >= griffen.mobileSize) {
project.header.animate({
'padding-left': '250px',
'padding-top': '0',
}, 1000, () => {
project.header.css({
'padding-left': '',
'padding-top': '',
});
});
} else {
project.header.animate({
'padding-left': '0',
'padding-top': '0',
}, 1000, () => {
project.header.css({
'padding-left': '',
'padding-top': '',
});
});
}
} else {
if (griffen.viewportWidth >= griffen.mobileSize) {
project.header.animate({
'padding-left': '250px',
}, 1000, () => {
project.header.css({
'padding-left': '',
});
});
}
}
};
if (project.container.scrollTop() !== 0) {
project.container.animate({
'scrollTop': 0,
}, 500, () => {
close();
});
} else {
close();
}
};
let toggleProject = (project) => {
'use strict';
if (griffen.projectVisible === false) {
if (griffen.projectOpening === false && griffen.projectClosing === false) {
griffen.projectOpening = true;
griffen.projectVisible = true;
openProject(project);
}
} else {
if (griffen.projectOpening === false && griffen.projectClosing === false) {
griffen.projectClosing = true;
closeProject(project);
}
}
};
let inciteWater = {
article: $('#incite-water'),
container: $('#incite-water-container'),
header: $('#incite-water-header'),
heading: $('#incite-water-heading'),
content: $('#incite-water-content'),
tabButton: $('#incite-water-button'),
id: 'incitewater',
};
console.log(inciteWater);
inciteWater.tabButton.click((e) => {
'use strict';
e.preventDefault();
$('.project-link').attr('aria-selected', 'false');
$('[role="tabpanel"]').attr('aria-hidden', 'true');
toggleProject(inciteWater);
});
let $closeInciteWater = $('#close-incite-water-button');
$closeInciteWater.click((e) => {
'use strict';
e.preventDefault();
$('.project-link').attr('aria-selected', 'false');
$('[role="tabpanel"]').attr('aria-hidden', 'true');
closeProject(inciteWater);
});
#introduction,
#sidebar {
box-sizing: border-box;
width: 100vw;
height: 100vh;
position: fixed;
opacity: 0;
z-index: -1;
}
.project,
.project-container {
overflow: hidden;
background-color: #fff
}
section>header {
text-align: center
}
section p {
font-size: 1.2em;
font-weight: 400;
line-height: 2;
margin: 1em 0
}
.project-date,
.project-header {
font-weight: 300;
text-transform: uppercase
}
.image {
font-size: 1em;
font-style: italic;
text-align: center
}
.icon,
.project-header {
-webkit-box-align: center;
display: -webkit-box
}
.project-content-title,
.project-header h3 {
font-family: "Krona One", "IBM Plex Sans", "Hiragino Maru Gothic ProN", sans-serif;
font-size: 1.6em
}
.project {
background-size: cover;
height: 40vh;
min-height: 250px;
position: relative;
width: 100%
}
.project-container,
.project-container>header {
height: 100%;
position: relative;
max-height: 100vh;
width: 100%
}
.project-container {
-webkit-overflow-scrolling: touch;
right: 0
}
.project-container>header {
overflow: hidden
}
.project-container.full .project-header,
.project-link {
position: absolute
}
.project-container.full {
/* overflow-x:hidden; */
;
overflow-y: scroll
}
.project-link {
clip: rect(auto, auto, auto, auto);
bottom: 0;
left: 0;
right: 0;
top: 0
}
.project-header {
-ms-flex-align: center;
align-items: center;
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color: #fff;
display: -ms-flexbox;
display: flex;
-webkit-box-orient: vertical;
-webkit-box-direction: reverse;
-ms-flex-direction: column-reverse;
flex-direction: column-reverse;
height: 60vh;
right: 0;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
max-height: 100vh;
position: fixed;
top: 50%;
-webkit-transform: translate3d(0, -50%, 0);
transform: translate3d(0, -50%, 0);
-webkit-transition: .25s opacity, .25s visibility, .25s top;
-o-transition: .25s opacity, .25s visibility, .25s top;
transition: .25s opacity, .25s visibility, .25s top;
width: 100%
}
#contact,
#contact-form {
-webkit-box-orient: vertical;
-webkit-box-direction: normal
}
.project-header h3 {
text-align: center
}
.project-pre-title {
margin: 0 0 2em;
position: relative
}
.project-pre-title::after {
background-color: #fff;
bottom: -1em;
content: '';
height: 1px;
left: 50%;
position: absolute;
-webkit-transform: translate3d(-50%, 0, 0);
transform: translate3d(-50%, 0, 0);
width: 8em
}
.project-content {
margin: 0 auto;
max-width: 960px;
padding: 2em;
position: relative;
z-index: 150;
}
.project-content-intro {
margin-bottom: 2em
}
.project-content-title {
text-transform: uppercase
}
.project-date {
margin: .5em 0
}
.close-project-button-dang {
cursor: pointer;
position: absolute;
right: 3em;
top: 3em;
width: 3em;
height: 3em;
background-image: url(../icons/cancel.svg);
background-size: cover;
opacity: 0;
-webkit-transition: .5s opacity;
-o-transition: .5s opacity;
transition: .5s opacity
}
#incite-water-heading {
background-image: url(https://toucan-sam.firebaseapp.com/assets/images/water-drops.jpeg);
background-position: center;
background-size: cover
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="introduction">
INTRODUCTION
</div>
<div id="sidebar">
SIDEBAR
</div>
<article class="project" id="incite-water">
<div class="project-container" id="incite-water-container">
<header id="incite-water-heading">
<a href="#incite-water" tabindex="0" class="project-link" id="incite-water-button" role="tab" aria-controls="incite-water-content" data-project="inciteWater">
<div class="project-header" id="incite-water-header">
<h3>Incite Water</h3>
<span class="project-pre-title">Web Design & Development</span>
</div>
</a>
</header>
<div class="project-content" tabindex="0" id="incite-water-content" aria-hidden="true" role="tabpanel" aria-labelledby="incite-water-button">
<div class="project-content-intro">
<div class="project-content-title">Incite Water</div>
<div class="project-date">October 2017 – March 2018</div>
</div>
<p>This year, for the Alaska Airlines Environmental Innovation Challenge, I worked on a project called Incite Water. Our team
was comprised of peers from various departments and the goal was to create a simple yet powerful interface that would encourage people to save water. We hoped to do this by demonstrating how much money people could personally save while mitigating
the potential environmental impacts of wasting water.</p>
<p>For this project, I worked as the sole web developer. I created the interface of the site as well as the background connections with our servers. In a small group, we programmed the “calculator” first using client-side JavaScript. I then implemented
the calculation using Node.js to run server-side to protect some sensitive information from exposure and to allow for easier implementation with the Firebase platform used to host the project.</p>
<p>My role went beyond web development and extended to providing direction for our technology-based decisions and assessing feasibility. I advised we use Google’s Firebase platform to take advantage the free low-traffic hosting and integration with
various database structures and cloud computing functions that enabled the quick prototyping of the product.</p>
<p>Though we chose to go with a high-fidelity prototype, it is still a prototype. We hoped that by presenting a more realized product to judges our vision would be more effectively conveyed. Because it is still an early prototype, we need to perform
additional user research and usability testing to determine our next course of action. I anticipate further adjustments to our calculator as well as increased outreach and awareness campaigns to involve more members of the public.</p>
<p>Visit Incite Water</p>
← Return<span class="sr-only"> to the main page</span>
</div>
</div>
</article>
Related
I have a simple on-hover CSS animation which makes slide transition between images.
When the user makes the hover on SECTION ONE and before the animation ends make hover on SECTION two, animation restart and make lagging move.
MY CODE:
var $circle = $('#circle');
function moveCircle(e) {
TweenLite.to($circle, 0.8, {
css: {
left: e.pageX,
top: e.pageY
}
});
}
$(window).on('mousemove', moveCircle);
#import "compass/css3";
#keyframes in {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
#keyframes out {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
html {
background: #0E3741;
}
#circle {
position: absolute;
pointer-events : none;
width: 400px;
height: 200px;
top: 50%;
left: 50%;
margin: -50px 0 0 -50px;
}
#circle .circle-wrapper {
overflow: hidden;
width: 400px;
height: 200px;
position: relative;
}
#circle img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 400px;
height: 200px;
object-fit: cover;
overflow: hidden;
}
#wrapper {
display: flex;
flex-direction: column;
}
.special-element {
width: 100%;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
#one {
background: blue;
}
#two {
background: red;
}
#one:hover ~ #circle .circle-wrapper #imgOne {
animation: in 1s ease-in-out;
z-index: 2;
}
#one:hover ~ #circle .circle-wrapper #imgTwo {
animation: out 1s ease-in-out;
}
#two:hover ~ #circle .circle-wrapper #imgTwo {
animation: in 1s ease-in-out;
z-index: 2;
}
#two:hover ~ #circle .circle-wrapper #imgOne {
animation: out 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenMax.min.js"></script>
<section id="wrapper">
<section class="special-element" id="one">
section one
</section>
<section class="special-element" id="two">
section two
</section>
<div id="circle">
<div class="circle-wrapper">
<img id="imgOne" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
<img id="imgTwo" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
</div>
</div>
</section>
Is there any solution how I can prevent this lagging issue?
Maybe is there any solution to how I can solve it and make this animation smooth?
I'm looking for something like animation on this website.
Updated version
You can do a simplified version of it with gsap. It is probably best not to mix plain css too much with the gsap, unless you use css inside the gsap library. Because gsap will manipulate some of the props. E.g. the transformation. And it is better to use transform than just left/top because it is hardware accelerated.
I've done some improvements to the code I've posted before. It looks smoother now. In addition, I have added a little zoom and horizontal-shift effect - similar to the animation on the referenced website. Also, the animation now starts from the bottom.
The animation is really well done on the referenced page. It is done with WebGL. This is not your every-day animation and requires quite a bit of effort to make it work - at least for someone who is not a designer. It uses a 3d transformation matrix and some other effects together.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset-css#5.0.1/reset.min.css" />
<script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/plugins/CSSPlugin.min.js"></script>
<style type="text/css">
.section {
display: block;
width: 100%;
height: 300px;
border-bottom: 1px solid red;
}
.overlay {
position: absolute;
top: 0;
left: 0;
display: none;
background: transparent;
z-index: -1;
}
.stack {
position: relative;
min-width: 300px;
min-height: 300px;
width: 480px;
height: 320px;
max-width: 480px;
max-height: 320px;
overflow: hidden;
z-index: 1;
}
.img {
position: absolute;
top: 0;
left: 0;
width: auto;
height: auto;
max-width: 100%;
object-fit: contain;
z-index: -1;
}
</style>
</head>
<body>
<main class="main">
<section class="section section-1" data-img="img-1">section 1</section>
<section class="section section-2" data-img="img-2">section 2</section>
<div class="overlay">
<div class="stack">
<img id="img-1" class="img" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
<img id="img-2" class="img" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
</div>
</div>
</main>
<script type="application/javascript">
window.onload = () => {
const overlay = document.querySelector(".overlay");
const stack = document.querySelector(".stack");
const s1 = document.querySelector(".section-1");
const s2 = document.querySelector(".section-2");
const main = document.querySelector(".main");
const overlaySize = {
width: 480,
height: 320
};
const easeFunc = "sine.inOut";
const easeDuration = 0.5;
let animation;
let activeSection;
let currentTarget;
function createAnimation() {
//console.log('create animation');
t1 = gsap.timeline({
paused: true
});
t1.to(currentTarget, {
zIndex: 2,
display: "block"
}, 0);
t1.fromTo(currentTarget, {
y: "100%"
}, {
y: 0,
duration: easeDuration,
ease: easeFunc
}, 0);
t1.to(currentTarget, {
scale: 1.25,
transformOrigin: "center",
duration: easeDuration,
ease: easeFunc
}, 0);
stack.querySelectorAll(".img").forEach((it) => {
if (it !== currentTarget) {
t1.to(it, {
zIndex: -1
}, 0);
t1.to(it, {
scale: 1,
transformOrigin: "center"
}, 0);
t1.to(it, {
display: "none"
}, easeDuration);
}
});
return t1;
}
function onMouseLeave(e) {
const target = e.target;
//console.log("leave", e.target);
if (target === activeSection) {
gsap.set(overlay, {
display: "none"
});
currentTarget = null;
}
}
function onMouseEnter(e) {
currentTarget = stack.querySelector(`#${e.target.dataset.img}`);
gsap.set(overlay, {
display: "block"
});
if (!animation) {
//console.log("undefined animation")
animation = createAnimation();
animation.play();
} else if (animation.isActive()) {
//console.log("still active");
animation.timeScale(10); // fast forward the rest of the animation
animation = createAnimation();
animation.timeScale(1).play();
} else {
//console.log("no longer active");
animation = createAnimation();
animation.play();
}
}
function onMouseMove(e) {
const hoveredEl = document.elementFromPoint(e.pageX, e.pageY);
if (hoveredEl.classList.contains("section")) {
if (activeSection !== hoveredEl) {
activeSection = hoveredEl;
}
} else if (hoveredEl.classList.contains("overlay") || hoveredEl.classList.contains("stack") || hoveredEl.classList.contains("pointer")) {
// do nothing
} else {
if (activeSection) {
activeSection = null;
}
}
if (currentTarget) {
// update overlay
gsap.set(overlay, {
x: e.pageX - overlaySize.width / 2,
y: e.pageY - overlaySize.height / 2
});
// add a little horizontal-shift effect
const dx = window.innerWidth / 2 - e.pageX;
const offsetX = dx / window.innerWidth / 2 * 100;
gsap.to(currentTarget, {
x: offsetX * 2,
duration: 2
}, 0);
}
}
gsap.set(overlay, {
x: 0,
y: 0
});
stack.querySelectorAll('.img').forEach((it) => gsap.set(it, {
x: 0,
y: "100%"
}));
window.addEventListener("mousemove", onMouseMove);
s1.addEventListener("mouseleave", onMouseLeave);
s2.addEventListener("mouseleave", onMouseLeave);
s1.addEventListener("mouseenter", onMouseEnter);
s2.addEventListener("mouseenter", onMouseEnter);
}
</script>
</body>
</html>
Old answer
I have been playing around a little bit with the gsap library today. I've honestly never done anything with or like it. Tried to do it with the x and y params that you may pass to gsap. It will take care of the transformations - also the TimeLine stuff
is quite handy. The result is not that great, also the animations look like it could be done better, but maybe it might still help you out. You could also improve some of the logic and animation probably. At least it runs quite stable - performance wise.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset-css#5.0.1/reset.min.css" />
<script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script type="application/javascript" defer src="https://cdnjs.cloudflare.com/ajax/libs/gsap/latest/plugins/CSSPlugin.min.js"></script>
<style type="text/css">
.section {
display: block;
width: 100%;
height: 200px;
border-bottom: 1px solid red;
}
.overlay {
position: absolute;
top: 0;
left: 0;
display: none;
border: none; // 1px dashed black;
background: transparent; // lavender;
overflow: hidden;
}
.stack {
position: relative;
left: 0;
right: 0;
top: 0;
bottom: 0;
min-width: 300px;
min-height: 300px;
width: 480px;
height: 320px;
z-index: 0;
}
.anim-img {
position: absolute;
top: 0;
left: 0;
width: auto;
height: auto;
max-width: 100%;
object-fit: contain;
z-index: 1;
}
</style>
</head>
<body>
<main class="main">
<section class="section section-1">section 1</section>
<section class="section section-2">section 2</section>
<div class="overlay">
<div class="stack">
<img id="img-1" class="anim-img" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
<img id="img-2" class="anim-img" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
</div>
</div>
</main>
<script type="application/javascript">
window.onload = () => {
const overlay = document.querySelector(".overlay");
const img1 = document.getElementById("img-1");
const img2 = document.getElementById("img-2");
const s1 = document.querySelector(".section-1");
const s2 = document.querySelector(".section-2");
const main = document.querySelector(".main");
let anim;
let isS1active = false;
let isS2active = false;
let showEl;
let hideEl;
let leaveTimeout;
function reverseFadeInOut(showEl, hideEl) {
console.log("create reverse timeline anim -> ", {
showEl,
hideEl
});
const tl = gsap.timeline({
paused: true
});
tl
.to(showEl, {
zIndex: 1
}, 0)
.to(hideEl, {
zIndex: 10
}, 0)
.to(hideEl, {
y: "-100%",
duration: 0.375
}, 0)
.to(hideEl, {
display: "none"
}, 0.375)
.to(hideEl, {
zIndex: 1
}, 0.375)
.to(showEl, {
display: "block",
zIndex: 10
}, 0.375)
.fromTo(showEl, {
y: "-100%"
}, {
y: 0,
duration: .375
}, 0.375)
.to(hideEl, {
display: "none"
});
return tl;
}
function fadeInOut(showEl, hideEl) {
console.log("create timeline anim -> ", {
showEl,
hideEl
});
const tl = gsap.timeline({
paused: true
});
tl
.to(hideEl, {
zIndex: 1
}, 0)
.to(showEl, {
display: "block",
zIndex: 10
}, 0)
.fromTo(showEl, {
y: "-100%"
}, {
y: 0,
duration: .75
}, 0)
.fromTo(hideEl, {
y: 0
}, {
y: "-100%",
duration: .75
}, 0)
.to(hideEl, {
display: "none"
}, 0.75);
return tl;
}
function animateImage() {
if (isS1active || isS2active) {
if (isS1active) {
showEl = img1;
hideEl = img2;
} else if (isS2active) {
showEl = img2;
hideEl = img1;
}
if (!anim) {
console.log("create new animation");
anim = fadeInOut(showEl, hideEl);
anim.play();
} else {
console.log("anim active:", anim.isActive());
if (anim.isActive()) {
console.log("reverse");
anim.kill();
anim = reverseFadeInOut(showEl, hideEl);
anim.play();
} else {
anim = fadeInOut(showEl, hideEl);
anim.play();
}
}
}
}
function moveOverlay(e) {
e.preventDefault();
e.stopPropagation();
gsap.set(overlay, {
x: e.pageX + 15,
y: e.pageY + 15,
display: isS1active || isS2active ? "block" : "none"
});
}
function mouseOver(e, el, isEntering) {
e.preventDefault();
e.stopPropagation();
el.classList.toggle("active");
isS1active = s1.classList.contains("active");
isS2active = s2.classList.contains("active");
if (isEntering) {
clearTimeout(leaveTimeout);
animateImage();
} else {
leaveTimeout = setTimeout(() => {
if (anim) {
console.log("kill anim");
anim.kill();
anim = null;
}
gsap.timeline({
onComplete: () => {
console.log("clear props");
gsap.set(".anim-img", {
clearProps: true
});
}
});
}, 500);
}
}
gsap.set(overlay, {
x: "0",
y: "0"
});
gsap.set(img1, {
x: "0",
y: "-100%"
});
gsap.set(img2, {
x: "0",
y: "-100%"
});
window.addEventListener("mousemove", moveOverlay);
s1.addEventListener("mouseenter", (e) => {
mouseOver(e, s1, true);
});
s1.addEventListener("mouseleave", (e) => {
mouseOver(e, s1, false);
});
s2.addEventListener("mouseenter", (e) => {
mouseOver(e, s2, true);
});
s2.addEventListener("mouseleave", (e) => {
mouseOver(e, s2, false);
});
}
</script>
</body>
</html>
I think that problem is because "moving circle function". Moving dom element with Left and right is not good for performance. You should move the circle with "transform". transform runs with GPU acceleration and it performs better and make move smooth.
Try this code.
function moveCircle(e) {
TweenLite.to($circle, 0.8, {
css: {
transform: `translate(${e.pageX}px, ${e.pageY}px)`
}
});
}
Yes, there is, by modifying the value of the second parameter of TweenLite.to, because that's the duration, see more here: http://www.tud.ttu.ee/im/Jaak.Henno/FlashDevelop/greensock-as3/greensock-as3/docs/com/greensock/TweenLite.html#to()
var $circle = $('#circle');
function moveCircle(e) {
TweenLite.to($circle, 0.1, {
css: {
left: e.pageX,
top: e.pageY
}
});
}
$(window).on('mousemove', moveCircle);
#import "compass/css3";
#keyframes in {
from {
transform: translateY(-100%);
}
to {
transform: translateY(0);
}
}
#keyframes out {
from {
transform: translateY(0);
}
to {
transform: translateY(100%);
}
}
html {
background: #0E3741;
}
#circle {
position: absolute;
pointer-events : none;
width: 400px;
height: 200px;
top: 50%;
left: 50%;
margin: -50px 0 0 -50px;
}
#circle .circle-wrapper {
overflow: hidden;
width: 400px;
height: 200px;
position: relative;
}
#circle img {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 400px;
height: 200px;
object-fit: cover;
overflow: hidden;
}
#wrapper {
display: flex;
flex-direction: column;
}
.special-element {
width: 100%;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
}
#one {
background: blue;
}
#two {
background: red;
}
#one:hover ~ #circle .circle-wrapper #imgOne {
animation: in 1s ease-in-out;
z-index: 2;
}
#one:hover ~ #circle .circle-wrapper #imgTwo {
animation: out 1s ease-in-out;
}
#two:hover ~ #circle .circle-wrapper #imgTwo {
animation: in 1s ease-in-out;
z-index: 2;
}
#two:hover ~ #circle .circle-wrapper #imgOne {
animation: out 1s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenMax.min.js"></script>
<section id="wrapper">
<section class="special-element" id="one">
section one
</section>
<section class="special-element" id="two">
section two
</section>
<div id="circle">
<div class="circle-wrapper">
<img id="imgOne" src="https://upload.wikimedia.org/wikipedia/commons/3/3b/Coca-cat.jpg">
<img id="imgTwo" src="https://staticcdn.sk/images/photoarchive/sized/700/2020/07/29/ohrozeny-vtak-krakla-belasa.jpg">
</div>
</div>
</section>
The website is using canvas for the moving slideshow and they've used webgl for the smooth kind of reveal effects.
<div class="site-footer">
<div class="js-scroll-height"></div>
<canvas width="780" height="624" class="js-webgl" style="width: 1041px;height: 833px;opacity: 1;border: 1px solid brown;"></canvas>
</div>
We can create similar reveal effect using simple animation and z-index manipulation:
var $cursor = $('#cursor');
function movecursor(e) {
TweenLite.to($cursor, 0.8, {
css: {
left: e.pageX,
top: e.pageY
}
});
}
$(window).on('mousemove', movecursor);
let topImg = null;
$('.special-element').mouseenter((e) => {
//make all images at same level
$('.img-wrapper').css({ zIndex: '1' });
//make last focused image at second highest level
if (topImg) topImg.css({ zIndex: '2' });
//make current image as topImage
let atr = $(e.target).attr('data-img');
topImg = $('.img-wrapper[data-img="' + atr + '"]');
//make it topmost
topImg.css({ zIndex: '3' });
});
:root {
--cursor-img-height: 30vh;
--cursor-img-width: 30vw;
}
html,
body {
background: #0E3741;
}
#wrapper {
display: flex;
flex-direction: column;
}
.special-element {
width: 100%;
height: 33vh;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
}
.special-element:nth-child(1) {
background: lightblue;
}
.special-element:nth-child(2) {
background: lightcoral;
}
.special-element:nth-child(3) {
background: lightgreen;
}
#cursor {
position: absolute;
pointer-events: none;
margin: 0;
margin-top: calc(var(--cursor-img-height) * -0.5);
margin-left: calc(var(--cursor-img-width) * -0.5);
overflow: hidden;
width: var(--cursor-img-width);
height: var(--cursor-img-height);
}
.img-wrapper {
position: absolute;
bottom: 0;
left: 0;
width: var(--cursor-img-width);
height: var(--cursor-img-height);
overflow: hidden;
}
.img-wrapper>img {
position: absolute;
bottom: 0;
left: 0;
width: var(--cursor-img-width);
height: var(--cursor-img-height);
object-fit: fill;
z-index: 1;
}
.special-element[data-img="one"]:hover~#cursor [data-img="one"],
.special-element[data-img="two"]:hover~#cursor [data-img="two"],
.special-element[data-img="three"]:hover~#cursor [data-img="three"] {
animation: slide .8s ease-in-out;
}
#keyframes slide {
from {
height: 0px;
transform: scale(1.2);
}
to {
height: (--cursor-img-height);
transform: scale(1);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/1.11.4/TweenMax.min.js"></script>
<section id="wrapper">
<section class="special-element" data-img="one">food</section>
<section class="special-element" data-img="two">animal</section>
<section class="special-element" data-img="three">night</section>
<div id="cursor">
<div class="img-wrapper" data-img='one'>
<img src="https://picsum.photos/id/674/400/200">
</div>
<div class="img-wrapper" data-img='two'>
<img src="https://picsum.photos/id/433/400/200">
</div>
<div class="img-wrapper" data-img='three'>
<img src="https://picsum.photos/id/901/400/200">
</div>
</div>
</section>
View in full page mode.
I have created a jQuery based slideshow that lives within a DIV on my webpage. The only problem is the images have no transition effect between each other, just one to the next without the first one slowly fading out and the next fading on.
I would like to crossfade these images. What am I missing in my JS?
var urls = ['https://example.com/front.png',
'https://example.com/interior-scaled.jpeg'];
var count = 1;
$('.hero').css('background-image', 'url("' + urls[0] + '")');
setInterval(function() {
$('.hero').css('background-image', 'url("' + urls[count] + '")');
count == urls.length-1 ? count = 0 : count++;
}, 6000);
});
LINK TO FIDDLE
If you are not opposed to using a jQuery slideshow library then may I suggest using Ken Wheelers Slick carousel jQuery lib.
slick.min.css minified size 1.4 KB
slick-theme.min.css minified size 3.07 KB (only use if you want slicks base theme styles)
slick.min.js minified size 51.9 KB
In your first comment you mentioned...
even if images slide like a carousel would be sufficient.
Well Slick makes easy work of both, plus loads of other cool options, event callbacks and responsive breakpoint settings. It might speed up creating sliding/fading carousels for your project utilising jQuery which you are already using.
I've include 2 hero slideshows in example below, both in fade: false mode.
#Hero_1 slideshow runs before images may have or have not loaded.
#Hero_2 uses $(window).on('load') to make sure your images have loaded before slideshow runs
// our hero examples as constant variables
const hero_1 = $('#hero_1');
const hero_2 = $('#hero_2');
// our slide image urls in constant variable array
const slides = [
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
];
// for each of the slide images as key > url
$.each(slides, function(key, url) {
// append slide to hero carousel div
$('.carousel', '.hero').append('<div class="slide" style="background-image:url(\'' + url + '\');"></div>');
});
// the below slick js should not run until the above each function has finished appending images in slides array
// slick hero carousel on init
$('.carousel', hero_1).on('init', function(slick) {
// add show class to hero div to animate height when slick init
$(hero_1).addClass('show');
// slick carousel options
}).slick({
slidesToShow: 1,
slidesToScroll: 1,
dots: false,
arrows: false,
fade: true,
adaptiveHeight: false,
autoplay: true,
infinite: true,
pauseOnFocus: false,
pauseOnHover: false,
autoplaySpeed: 4000,
speed: 1000,
draggable: false
});
// use this if you want all background images to load first
// tho may be slow to run depending on how many images and the image size you are loading
$(window).on('load', function() {
// slick on init
$('.carousel', hero_2).on('init', function(slick) {
// add show class to hero div to expand height
$(hero_2).addClass('show');
// slick options
}).slick({
slidesToShow: 1,
slidesToScroll: 1,
dots: false,
arrows: false,
fade: true,
adaptiveHeight: false,
autoplay: true,
infinite: true,
pauseOnFocus: false,
pauseOnHover: false,
autoplaySpeed: 4000,
speed: 1000,
draggable: false
});
});
.hero {
position: relative;
overflow: hidden;
background: rgba(0, 0, 0, .75);
min-height: 0;
height: 0;
transition: all 0.5s ease;
margin: 0 0 .5rem 0;
}
.hero.show {
min-height: 150px;
height: 150px;
/*
height:45%;
height:45vh;
min-height:400px;
*/
}
.hero .carousel {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s ease;
}
.hero .carousel.slick-initialized {
opacity: 1;
}
.hero .carousel .slick-list,
.hero .carousel .slick-track {
height: 100% !important;
}
.hero .carousel .slide {
background-color: none;
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
height: 100%;
}
.hero .overlay {
color: #fff;
position: relative;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
}
/* for demo styling purposes */
BODY {
font-family: helvetica;
}
H1 {
font-size: 2rem;
font-weight: 600;
margin: 0 0 .5rem 0;
}
P {
margin: 0 0 .5rem 0;
}
.lead {
font-size: 1.4rem;
margin: 0 0 .5rem 0;
}
.row {
margin: 0 -4px 0 -4px;
}
.col {
float: left;
width: calc(50% - 8px);
padding: 0 4px 0 4px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet" />
<div class="row">
<div class="col">
<p>
<code><strong>#hero_1</strong><br/></code>
<code><small>Slick inits after each function is complete.</small></code>
</p>
<div id="hero_1" class="hero">
<div class="carousel"></div>
<div class="overlay">
<h1>Hero 1</h1>
<p class="lead">
Tooth Hurty
</p>
</div>
</div>
</div>
<div class="col">
<p>
<code><strong>#hero_2</strong></code><br/>
<code><small>Waits for all imgs to load before init slick.</small></code>
</p>
<div id="hero_2" class="hero">
<div class="carousel"></div>
<div class="overlay">
<h1>Hero 2</h1>
<p class="lead">
Tooth Hurty
</p>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
Here is the same code above but in fade: false mode...
#Hero_1 slideshow runs before images may have or have not loaded.
#Hero_2 uses $(window).on('load') to make sure your images have loaded before slideshow runs
// our hero examples as constant variables
const hero_1 = $('#hero_1');
const hero_2 = $('#hero_2');
// our slide image urls in constant variable array
const slides = [
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
];
// for each of the slide images as key > url
$.each(slides, function(key, url) {
// append slide to hero carousel div
$('.carousel', '.hero').append('<div class="slide" style="background-image:url(\'' + url + '\');"></div>');
});
// the below slick js should not run until the above each function has finished appending images in slides array
// slick hero carousel on init
$('.carousel', hero_1).on('init', function(slick) {
// add show class to hero div to animate height when slick init
$(hero_1).addClass('show');
// slick carousel options
}).slick({
slidesToShow: 1,
slidesToScroll: 1,
dots: false,
arrows: false,
fade: false,
adaptiveHeight: false,
autoplay: true,
infinite: true,
pauseOnFocus: false,
pauseOnHover: false,
autoplaySpeed: 4000,
speed: 1000,
draggable: false
});
// use this if you want all background images to load first
// tho may be slow to run depending on how many images and the image size you are loading
$(window).on('load', function() {
// slick on init
$('.carousel', hero_2).on('init', function(slick) {
// add show class to hero div to expand height
$(hero_2).addClass('show');
// slick options
}).slick({
slidesToShow: 1,
slidesToScroll: 1,
dots: false,
arrows: false,
fade: false,
adaptiveHeight: false,
autoplay: true,
infinite: true,
pauseOnFocus: false,
pauseOnHover: false,
autoplaySpeed: 4000,
speed: 1000,
draggable: false
});
});
.hero {
position: relative;
overflow: hidden;
background: rgba(0, 0, 0, .75);
min-height: 0;
height: 0;
transition: all 0.5s ease;
margin: 0 0 .5rem 0;
}
.hero.show {
min-height: 150px;
height: 150px;
/*
height:45%;
height:45vh;
min-height:400px;
*/
}
.hero .carousel {
position: absolute;
top: 0;
right: 0;
width: 100%;
height: 100%;
opacity: 0;
transition: opacity 0.5s ease;
}
.hero .carousel.slick-initialized {
opacity: 1;
}
.hero .carousel .slick-list,
.hero .carousel .slick-track {
height: 100% !important;
}
.hero .carousel .slide {
background-color: none;
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
height: 100%;
}
.hero .overlay {
color: #fff;
position: relative;
text-align: center;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-shadow: 1px 1px 2px rgba(0, 0, 0, .75);
}
/* for demo styling purposes */
BODY {
font-family: helvetica;
}
H1 {
font-size: 2rem;
font-weight: 600;
margin: 0 0 .5rem 0;
}
P {
margin: 0 0 .5rem 0;
}
.lead {
font-size: 1.4rem;
margin: 0 0 .5rem 0;
}
.row {
margin: 0 -4px 0 -4px;
}
.col {
float: left;
width: calc(50% - 8px);
padding: 0 4px 0 4px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet" />
<div class="row">
<div class="col">
<p>
<code><strong>#hero_1</strong><br/></code>
<code><small>Slick inits after each function is complete.</small></code>
</p>
<div id="hero_1" class="hero">
<div class="carousel"></div>
<div class="overlay">
<h1>Hero 1</h1>
<p class="lead">
Tooth Hurty
</p>
</div>
</div>
</div>
<div class="col">
<p>
<code><strong>#hero_2</strong></code><br/>
<code><small>Waits for all imgs to load before init slick.</small></code>
</p>
<div id="hero_2" class="hero">
<div class="carousel"></div>
<div class="overlay">
<h1>Hero 2</h1>
<p class="lead">
Tooth Hurty
</p>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
You can use transition: background-image. It might not be supported in all browsers, but most modern browsers should be fine.
Add
-webkit-transition: background-image 0.5s ease-in-out;
transition: background-image 0.5s ease-in-out;
to the css of the div which has the background image.
Here's a forked fiddle with a working example: https://jsfiddle.net/bmh2qu0e/1/
You can use transition on opacity and toggle opacity on background change, something like:
$(document).ready(function() {
var urls = ['https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
];
var count = 1;
var $hero = $('.hero');
$hero.css('background-image', 'url("' + urls[0] + '")');
setInterval(function() {
setTimeout(function() {
$hero.toggleClass('transparent');
setTimeout(function() {
$hero.css('background-image', 'url("' + urls[count] + '")');
count == urls.length - 1 ? count = 0 : count++;
$hero.toggleClass('transparent');
}, 300);
}, 300);
}, 6000);
});
.transparent {
opacity: 0;
}
.hero {
height: 45%;
height: 45vh;
min-height: 400px;
background-color: none;
text-align: center;
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
-webkit-transition: opacity 0.5s ease-in-out;
transition: opacity 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="hero"></div>
If you want to take a step further, you can make it more generic with class:
class ImageSlider {
imagePos = 0;
intevalHandle = null;
intervalMS = 6000;
constructor(elem, images, startImmediately) {
this.images = images || [];
this.elem = $(elem);
if (startImmediately) {
this.startSlider();
}
}
startSlider() {
if (this.startTimer()) {
this.imagePos = 0;
this.onTimerInterval();
}
};
pauseSlider() {
this.clearTimer();
}
resumeSlider() {
this.startTimer();
}
stopSlider() {
this.clearTimer();
this.imagePos = 0;
};
startTimer() {
if (this.intervalHandle != null) {
return false;
}
this.intervalHandle = setInterval(() => this.onTimerInterval(), this.intervalMS);
return true;
};
clearTimer() {
if (this.intervalHandle) {
this.clearInterval(this.intervalHandle);
this.intervalHandle = null;
}
}
onTimerInterval() {
if (this.images.length <= 0) {
return;
}
setTimeout(() => {
this.elem.toggleClass('transparent');
setTimeout(() => {
if (this.imagePos >= this.images.length) {
this.imagePos = 0;
}
this.elem.css('background-image', 'url("' + this.images[this.imagePos] + '")');
this.imagePos++;
this.elem.toggleClass('transparent');
}, 300);
}, 300);
}
}
$(document).ready(function() {
var urls = ['https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'
];
var slider1 = new ImageSlider('#ss1', urls, true);
var slider2 = new ImageSlider('#ss2', [...urls].reverse(), true);
});
.transparent {
opacity: 0;
}
.hero {
height: 45%;
height: 45vh;
min-height: 400px;
background-color: none;
text-align: center;
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
-webkit-transition: opacity 0.5s ease-in-out;
transition: opacity 0.5s ease-in-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="ss1" class="hero"></div>
<div id="ss2" class="hero"></div>
You can't cross-fade a single background image using CSS.
A possible solution is to have two containers inside the hero <div> you have there.
E.g:
<div class="hero">
<div class="img-container" id="first"></div>
<div class="img-container" id="second"></div>
</div>
For your desired effect of the crossfade you will need these images to cover the desired area on top of the hero <div>.
This can be done by these CSS rules:
.img-container {
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
background-color: transparent;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
Now we need to have the images load in and cross-fade over one another.
$(document).ready(function() {
var urls = [
'imgURL1',
'imgURL2',
'imgURL3'
];
// Preload the images
var tempImg = []
for (var i = 0; i < urls.length; i++) {
(new Image()).src = urls[i]
}
// The currently shown image's index
var currentShown = 0;
// Get the containers
var first = $("#first");
var second = $("#second");
// This shows whether the second object is on top or not
var secondOnTop = true;
// Set the first container's value so that there is something on the screen and load the second image on top.
first.css('background-image', 'url("' + urls[urls.length - 1] + '")');
second.css({
backgroundImage: 'url("' + urls[0] + '")',
opacity: 1
});
// Change the image every X seconds
setInterval(function() {
var animationSpeed = 1000; // In milliseconds
// Increment currently shown image index
currentShown === urls.length - 1 ? currentShown = 0 : currentShown++;
// Determine which object has visual priority
var primaryObj = first;
var auxObj = second;
if (secondOnTop) {
primaryObj = second;
auxObj = first;
}
secondOnTop = !secondOnTop;
// Show aux obj background
auxObj.css({backgroundImage: 'url("' + urls[currentShown] + '")'});
auxObj.animate({
opacity: 1
}, animationSpeed);
// Change shown object's background and set to 0
primaryObj.animate({
opacity: 0,
}, animationSpeed, function() {
// Change the primary's background to the next in queue
var nextImg = currentShown === urls.length - 1 ? 0 : currentShown + 1;
primaryObj.css('background-image', 'url("' + urls[nextImg] + '")');
});
}, 6000);
});
I have created a fork of your fiddle available here: https://jsfiddle.net/YeloPartyHat/uLfr389g/88/
Here is a solution: (I had to replace shortened url with full url otherwise SO wouldn't let me save the answer)
$(document).ready(function(){
var urls = ['https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-patient-exam-room.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-interior-scaled.jpeg',
'https://concorddentalde.com/wp-content/uploads/2020/10/concord-dental-front.png'];
var totalLayers = 2;
var layerIndex = 0;
var count = 0;
$('.layer-' + layerIndex)
.removeClass('layer')
.css('background-image', 'url("' + urls[count] + '")');
console.log({first: layerIndex, second: (layerIndex + 1) % totalLayers, count})
setInterval(function() {
var outLayer = layerIndex
var inLayer = ++layerIndex % totalLayers
layerIndex = inLayer
count = ++count % urls.length;
console.log({first: outLayer, second: inLayer, count})
$('.layer-' + outLayer)
.addClass('animateXFadeOut');
$('.layer-' + inLayer)
.removeClass('layer')
.css('background-image', 'url("' + urls[count] + '")')
.addClass('animateXFadeIn');
setTimeout(function() {
$('.layer-' + outLayer).css({backgroundImage: 'none', opacity: 1});
$('.layers').removeClass('animateXFadeIn animateXFadeOut');
}, 1000);
}, 6000);
});
.hero {
/* height: 45%;
height: 45vh;
min-height: 400px;
*/
background-color: none;
text-align: center;
background-repeat: no-repeat;
background-size: cover;
background-position: 50% 50%;
}
#keyframes xfadein {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
#keyframes xfadeout {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.animateXFadeIn {
animation-name: xfadein;
animation-duration: 1s;
}
.animateXFadeOut {
animation-name: xfadeout;
animation-duration: 1s;
}
.layer-0, .layer-1 {
display: block;
position: absolute;
height: 45%;
height: 45vh;
min-height: 400px;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="hero">
<div class="layers layer-0"></div>
<div class="layers layer-1"></div>
</div>
you can use one little class and one line of jquery to do that
$(document).ready(function(){
var urls = ['image_one_url',
'image_two_url',
'image_three_url'];
var count = 1;
$('.hero').css('background-image', 'url("' + urls[0] + '")');
$('.hero').addClass('animatedinout');
setInterval(function() {
$('.hero').css('background-image', 'url("' + urls[count] + '")');
count == urls.length-1 ? count = 0 : count++;
}, 6000);
});
.animatedinout{
animation: fadeinout;
animation-duration: 6000ms;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
#keyframes fadeinout{
0%{
opacity: 0;
}
10%{
opacity: 1;
}
90%{
opacity: 1;
}
100%{
opacity: 0;
}
}
i just add a css class called animatedinout that use an animation forever for every 6000 mili seconds and add
$('.hero').addClass('animatedinout');
right before your setInterval.
I thank everyone for their hard work. The solution I found (because I was on a tight deadline) is actually pretty simple and combines JS and CSS to make for a "true" crossfade transition.
HTML:
<div id="background-images">
<div class="bgImages active" id="bgImg1"></div>
<div class="bgImages" id="bgImg2"><br></div>
<div class="bgImages" id="bgImg3"><br><br></div>
<div class="bgImages" id="bgImg4"><br></div>
<div class="bgImages" id="bgImg5"><br><br></div>
</div>
jQuery:
function cycleImages() {
var $active = $("#background-images .active");
var $next = $active.next().length > 0
? $active.next()
: $("#background-images div:first");
$next.css("z-index", 2); // move the next img up the stack
$active.fadeOut(1500, function() {
//fade out the top image
$active.css("z-index", 1).show().removeClass("active"); //reset z-index and unhide the image
$next.css("z-index", 3).addClass("active"); //make the next image the top one
});
}
$(document).ready(function() {
$("#cycler img").show();
// run every 6 seconds
setInterval(cycleImages, 6000);
});
CSS:
#background-images {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 670px;
z-index: -5;
}
#bgImg1, #bgImg2, #bgImg3, #bgImg4, #bgImg5 {
width: 100%;
height: 100%;
position: fixed;
background-position-x: center;
background-position-y: center;
background-size: cover;
background-repeat: no-repeat;
}
#bgImg1 { background-image: url("https://image1.jpg"); }
#bgImg2 { background-image: url("https://image2.png"); z-index: 2; }
#bgImg3 { background-image: url("https://image3.jpeg"); }
#bgImg4 { background-image: url("https://image4.jpg"); }
#bgImg5 { background-image: url("https://image5.jpeg"); }
It was a clever way of using z-index combined with active status to get the images to actually crossfade with no white "blink".
Found it on a CodePen here.
I've done a research on this question and found some solutions. However, not every one of them worked. As I understand, async false makes a UI block, which shouldn't be. I could use an overlay until ajax request is completed and on the request success, hide the overlay.
That was my try using a callback argument in the getNewQuote() function (only a small snippet of it):
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
callback();
return quote;
}, 4000);
};
getNewQuote(function() {
console.log("DONE");
var getRandomColor = function() {
var colors = [
"#ff9966",
"#7f00ff",
"#396afc",
"#0cebeb",
"#06beb6",
"#642b73",
"#36d1dc",
"#cb356b",
"#3a1c71",
"#ef3b36",
"#159957",
"#000046",
"#007991",
"#56ccf2",
"#f2994a",
"#e44d26",
"#4ac29a",
"#f7971e",
"#34e89e",
"#6190e8",
"#3494e6",
"#ee0979"
],
randomNumber = Math.floor(Math.random() * colors.length);
return colors[randomNumber];
};
var updateText = function($t, qt) {
var twitter = "https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=";
twitter += '"' + qt.text + '" ';
twitter += qt.author;
var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes,freecodecamp&caption=";
tumblr += qt.author;
tumblr += "&content=";
tumblr += qt.text;
tumblr += "&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&shareSource=tumblr_share_button";
var $icon = $("<i class='fa fa-quote-left'>")
.prop("aria-hidden", true);
$t.find(".quote-text").html("").append($icon, qt.text);
$t.find(".quote-author").html("- " + qt.author);
$("#tweet-quote").attr("href", twitter);
$("#tumblr-quote").attr("href", tumblr);
};
var calcNewHeight = function(q) {
var $temp = $("<div>", {
class: "quote-container temp",
}).appendTo($("body"));
$temp.append($("<div>", {
class: "quote-text"
}), $("<div>", {
class: "quote-author"
}));
updateText($temp, q);
var h = $temp.height() + 40;
$temp.remove();
return h;
};
var changeColor = function(newColor) {
$("body, .button:not(#new-quote)").animate({
backgroundColor: newColor
});
$("#new-quote").animate({
color: newColor
});
$(".quote-text, .quote-author").css("color", newColor);
if ($("#modStyle").length === 0) {
$("head").append(
"<style id='modStyle'>#new-quote:before {background:" + newColor + ";}</style>"
);
} else {
$("head style#modStyle").html("#new-quote:before {background:" + newColor + ";}");
}
};
var getQuote = function() {
var nq, nc, nh = 0;
nq = getNewQuote();
nc = getRandomColor();
nh = calcNewHeight(nq);
$(".quote-container").children().css("opacity", 0);
changeColor(nc);
$(".quote-container, #new-quote").animate({
height: nh,
}, {
duration: 1000,
queue: false
});
$(".quote-container").animate({
padding: "2.5em"
}, {
duration: 1000,
queue: false
});
$("#new-quote").animate({
padding: "2.5em .75em"
}, {
duration: 1000,
queue: false
});
updateText($(".quote-container"), nq);
$(".quote-container").children().fadeTo(750, 1);
};
$("#new-quote").on("click", getQuote);
$(".quote-container, #new-quote").css({
visibility: "visible",
height: 0
});
$("#new-quote").css("padding", "0 .75em");
getQuote();
});
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
background: #333;
color: #333;
font-family: sans-serif;
}
.v-wrap {
height: 100%;
text-align: center;
}
.v-wrap:before {
content: "";
display: inline-block;
vertical-align: middle;
width: 0;
height: 100%;
}
.quote-container {
width: 31.25rem;
background: #fff;
margin: 0;
display: inline-block;
vertical-align: middle;
border-radius: 0.1875rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
visibility: hidden;
padding: 0 2.5rem;
}
.quote-text {
font-size: 1.625rem;
}
.quote-text i {
margin-right: 0.6rem;
}
.quote-text p {
display: inline;
}
.quote-author {
font-size: 1rem;
margin: 0 0.4rem 2rem 0;
text-align: right;
}
.button {
padding: 0.75rem;
text-align: center;
font-size: 1rem;
color: #fff;
border-radius: .1875rem;
display: inline-block;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.button:not(#new-quote):hover {
opacity: .8 !important;
}
.button:not(#new-quote) {
min-width: 1rem;
min-height: 1rem;
}
.button i {
vertical-align: middle;
}
#new-quote {
white-space: nowrap;
writing-mode: vertical-lr;
height: 50%;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
vertical-align: middle;
background: #fff !important;
margin: 0;
position: relative;
right: 0.25625rem;
color: #333;
visibility: hidden;
}
#new-quote:before {
content: "";
position: absolute;
height: 100%;
width: 0.0625rem;
bottom: 0;
left: 0;
visibility: hidden;
-webkit-transform: scaleY(0);
transform: scaleY(0);
-webkit-transition: all .3s ease-in-out;
transition: all .3s ease-in-out;
}
#new-quote:hover:before {
visibility: visible;
-webkit-transform: scaleY(1);
transform: scaleY(1);
}
footer {
font-size: 0.85rem;
margin-bottom: 1rem;
}
footer a {
text-decoration: none;
color: #fff;
position: relative;
}
footer a:before {
content: "";
position: absolute;
width: 100%;
height: .0625rem;
bottom: 0;
left: 0;
background: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all .3s ease-in-out 0s;
transition: all .3s ease-in-out 0s;
}
footer a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="v-wrap">
<div class="quote-container" style="">
<div class="quote-text">
</div>
<div class="quote-author"></div>
<a id="tweet-quote" class="button"><i class="fa fa-twitter"></i></a>
<a id="tumblr-quote" class="button"><i class="fa fa-tumblr"></i></a>
</div>
<div id="new-quote" class="button">New quote</div>
<footer>
Created by LukasLSC
</footer>
</div>
Code output:
As you can see, ajax wasn't success
Uncaught TypeError: callback is not a function and Uncaught TypeError: Cannot read property 'text' of undefined (only in the stack snippet).
I found out this error disappears if I remove the getQuote(); function call. However, I need to call it, overwise, my project won't work. I also tried to use return $.ajax but there was a return quote line so I couldn't use it. The full code can be found here on codepen: https://codepen.io/Kestis500/pen/ZvyxKB?editors=0110.
Then I switched to another method using jQuery promises and used information in this thread: https://stackoverflow.com/a/40658281/8889739. Full code: https://codepen.io/Kestis500/pen/qpjxoq?editors=0110.
var MyFirstFunction = function() {
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
return quote;
}, 4000);
};
}
var MySecondFunction = function() {
console.log("DONE");
var getRandomColor = function() {
var colors = [
"#ff9966",
"#7f00ff",
"#396afc",
"#0cebeb",
"#06beb6",
"#642b73",
"#36d1dc",
"#cb356b",
"#3a1c71",
"#ef3b36",
"#159957",
"#000046",
"#007991",
"#56ccf2",
"#f2994a",
"#e44d26",
"#4ac29a",
"#f7971e",
"#34e89e",
"#6190e8",
"#3494e6",
"#ee0979"
],
randomNumber = Math.floor(Math.random() * colors.length);
return colors[randomNumber];
};
var updateText = function($t, qt) {
var twitter = "https://twitter.com/intent/tweet?hashtags=quotes&related=freecodecamp&text=";
twitter += '"' + qt.text + '" ';
twitter += qt.author;
var tumblr = "https://www.tumblr.com/widgets/share/tool?posttype=quote&tags=quotes,freecodecamp&caption=";
tumblr += qt.author;
tumblr += "&content=";
tumblr += qt.text;
tumblr += "&canonicalUrl=https%3A%2F%2Fwww.tumblr.com%2Fbuttons&shareSource=tumblr_share_button";
var $icon = $("<i class='fa fa-quote-left'>")
.prop("aria-hidden", true);
$t.find(".quote-text").html("").append($icon, qt.text);
$t.find(".quote-author").html("- " + qt.author);
$("#tweet-quote").attr("href", twitter);
$("#tumblr-quote").attr("href", tumblr);
};
var calcNewHeight = function(q) {
var $temp = $("<div>", {
class: "quote-container temp",
}).appendTo($("body"));
$temp.append($("<div>", {
class: "quote-text"
}), $("<div>", {
class: "quote-author"
}));
updateText($temp, q);
var h = $temp.height() + 40;
$temp.remove();
return h;
};
var changeColor = function(newColor) {
$("body, .button:not(#new-quote)").animate({
backgroundColor: newColor
});
$("#new-quote").animate({
color: newColor
});
$(".quote-text, .quote-author").css("color", newColor);
if ($("#modStyle").length === 0) {
$("head").append(
"<style id='modStyle'>#new-quote:before {background:" + newColor + ";}</style>"
);
} else {
$("head style#modStyle").html("#new-quote:before {background:" + newColor + ";}");
}
};
var getQuote = function() {
var nq, nc, nh = 0;
nq = getNewQuote();
nc = getRandomColor();
nh = calcNewHeight(nq);
$(".quote-container").children().css("opacity", 0);
changeColor(nc);
$(".quote-container, #new-quote").animate({
height: nh,
}, {
duration: 1000,
queue: false
});
$(".quote-container").animate({
padding: "2.5em"
}, {
duration: 1000,
queue: false
});
$("#new-quote").animate({
padding: "2.5em .75em"
}, {
duration: 1000,
queue: false
});
updateText($(".quote-container"), nq);
$(".quote-container").children().fadeTo(750, 1);
};
$("#new-quote").on("click", getQuote);
$(".quote-container, #new-quote").css({
visibility: "visible",
height: 0
});
$("#new-quote").css("padding", "0 .75em");
getQuote();
}
MyFirstFunction().done(MySecondFunction);
html,
body {
height: 100%;
width: 100%;
}
body {
margin: 0;
padding: 0;
background: #333;
color: #333;
font-family: sans-serif;
}
.v-wrap {
height: 100%;
text-align: center;
}
.v-wrap:before {
content: "";
display: inline-block;
vertical-align: middle;
width: 0;
height: 100%;
}
.quote-container {
width: 31.25rem;
background: #fff;
margin: 0;
display: inline-block;
vertical-align: middle;
border-radius: 0.1875rem;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
visibility: hidden;
padding: 0 2.5rem;
}
.quote-text {
font-size: 1.625rem;
}
.quote-text i {
margin-right: 0.6rem;
}
.quote-text p {
display: inline;
}
.quote-author {
font-size: 1rem;
margin: 0 0.4rem 2rem 0;
text-align: right;
}
.button {
padding: 0.75rem;
text-align: center;
font-size: 1rem;
color: #fff;
border-radius: .1875rem;
display: inline-block;
cursor: pointer;
-webkit-user-select: none;
user-select: none;
}
.button:not(#new-quote):hover {
opacity: .8 !important;
}
.button:not(#new-quote) {
min-width: 1rem;
min-height: 1rem;
}
.button i {
vertical-align: middle;
}
#new-quote {
white-space: nowrap;
writing-mode: vertical-lr;
height: 50%;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
vertical-align: middle;
background: #fff !important;
margin: 0;
position: relative;
right: 0.25625rem;
color: #333;
visibility: hidden;
}
#new-quote:before {
content: "";
position: absolute;
height: 100%;
width: 0.0625rem;
bottom: 0;
left: 0;
visibility: hidden;
-webkit-transform: scaleY(0);
transform: scaleY(0);
-webkit-transition: all .3s ease-in-out;
transition: all .3s ease-in-out;
}
#new-quote:hover:before {
visibility: visible;
-webkit-transform: scaleY(1);
transform: scaleY(1);
}
footer {
font-size: 0.85rem;
margin-bottom: 1rem;
}
footer a {
text-decoration: none;
color: #fff;
position: relative;
}
footer a:before {
content: "";
position: absolute;
width: 100%;
height: .0625rem;
bottom: 0;
left: 0;
background: #fff;
visibility: hidden;
-webkit-transform: scaleX(0);
transform: scaleX(0);
-webkit-transition: all .3s ease-in-out 0s;
transition: all .3s ease-in-out 0s;
}
footer a:hover:before {
visibility: visible;
-webkit-transform: scaleX(1);
transform: scaleX(1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="v-wrap">
<div class="quote-container" style="">
<div class="quote-text">
</div>
<div class="quote-author"></div>
<a id="tweet-quote" class="button"><i class="fa fa-twitter"></i></a>
<a id="tumblr-quote" class="button"><i class="fa fa-tumblr"></i></a>
</div>
<div id="new-quote" class="button">New quote</div>
<footer>
Created by LukasLSC
</footer>
</div>
Code output:
It broke everything, the gray screen is because of the default codepen background
Uncaught TypeError: Cannot read property 'done' of undefined
You're using return from the asynchronous operation's callback. That just sets the return value of that callback (which is ignored in the case of setTimeout's or XHR's callback), it doesn't set the return value of your function.
You can't return the value from your function, which is why you're adding a callback. Instead:
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
callback(quote); // <====
}, 4000);
};
...and use the parameter of the callback, e.g.:
getNewQuote(function(quote) {
// Use quote here...
});
Live Example:
var getNewQuote = function(callback) {
var quote = {};
setTimeout(function() {
quote.text = 'Example';
quote.author = 'Example';
callback(quote); // <====
}, 1000);
};
getNewQuote(function(quote) {
console.log("quote:", quote);
});
I want to make a draggle splitter between 2 panels. The following is a working version.
Now, I want to make the width of handle as thin as possible (less than 0.1px?), so there is no way to make the width (appear) smaller than 1px?
Additionally, when the splitter is thin, it is hard to select by the mouse. Is there a way to make a splitter easy to grab?
Taking JSBin as example, how did they manage to realise the splitters among the panels?
(function($) {
$.fn.drags = function(opt) {
opt = $.extend({
handle: "",
cursor: "ew-resize",
min: 10
}, opt);
if (opt.handle === "") {
var $el = this;
} else {
var $el = this.find(opt.handle);
}
var priorCursor = $('body').css('cursor');
return $el.css('cursor', opt.cursor).on("mousedown", function(e) {
priorCursor = $('body').css('cursor');
$('body').css('cursor', opt.cursor);
if (opt.handle === "") {
var $drag = $(this).addClass('draggable');
} else {
var $drag = $(this).addClass('active-handle').parent().addClass('draggable');
}
var z_idx = $drag.css('z-index'),
drg_h = $drag.outerHeight(),
drg_w = $drag.outerWidth(),
pos_y = $drag.offset().top + drg_h - e.pageY,
pos_x = $drag.offset().left + drg_w - e.pageX;
var mouseMove = function(e) {
var prev = $('.draggable').prev();
var next = $('.draggable').next();
var total = prev.outerWidth() + next.outerWidth();
var totalPercentage = parseFloat(prev.css('flex')) + parseFloat(next.css('flex'));
var offset = prev.offset();
if(offset){
var leftPercentage = ((e.pageX - offset.left - drg_w / 2) / total) * totalPercentage;
var rightPercentage = totalPercentage - leftPercentage;
if (leftPercentage * 100 < opt.min || rightPercentage * 100 < opt.min) {
return;
}
prev.css('flex', leftPercentage.toString());
next.css('flex', rightPercentage.toString());
}
}
$drag.css('z-index', 1000).parent().on("mousemove", mouseMove).on("mouseup", function() {
$(this).off("mousemove", mouseMove).off("mouseup");
$('body').css('cursor', priorCursor);
$('.draggable').removeClass('draggable').css('z-index', z_idx);
});
e.preventDefault(); // disable selection
});
}
})(jQuery);
$('.handle').drags();
.flex-box {
display: flex;
width: 100%;
margin: 0;
height: 300px;
}
.flex-box .col {
border: 1px solid grey;
flex: 0.33;
padding: 12px;
overflow-y: auto;
overflow-x: hide;
}
.handle {
width: 1px;
text-align: center;
background: grey;
transition: all ease-in 0.1s;
}
.draggable {
background: grey;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="flex-box">
<div class="col">
<p>Pellentesque ...</p>
</div>
<div class="handle"></div>
<div class="col">
<p>Pellentesque ...</p>
</div>
</div>
If you'd like the handle to appear thinner try applying a negative value to the right "col" e.g. margin-left: -2px; so it overlaps the left "col" border on the left of it. I don't think you can make the width "appear" as 0.1px. Firefox is the only browser that renders such value. (https://css-tricks.com/forums/topic/0-1px-borders/)
.flex-box .col:last-child {
margin-left: -2px;
}
//raise handle layer to top
.handle {
.....
z-index: 9999;
}
Hope this helps...
*Edit:
This is the closest I could get to your request:
.flex-box {
display: flex;
width: 100%;
margin: 0;
height: 300px;
}
.flex-box .col {
border: 1px solid grey;
flex: 0.33;
padding: 12px;
overflow-y: auto;
overflow-x: hide;
}
.flex-box .col:last-child {
margin-left: -6px;
}
.handle {
width: 5px;
text-align: center;
transition: all ease-in 0.1s;
z-index: 999;
overflow: visible;
}
.handle-inner{
width: 5px;
height: 100%;
position: relative;
margin-left: -10px;
}
.draggable {
background: grey;
}
Jsbin :
https://jsbin.com/nupefekuhu/edit?html,css,js,output
I have a fixed DIV and 3 sections A,B,C.
I'm using jquery scrollify for changing data of sections and I want to change specific div data on scroll of mouse wheel.
As you know, scrollify allow us to scroll a section on mouse wheel rotation, when section change I need to update that fixed div data withn respective section contents
section height and width will be screen height and screen width,
I asked almost the same question with using scrollify website example and I didnt get any response, so I decided to ask again with graphical example
This is parallax what you need to do is to look at this example
http://codepen.io/hudsonmarinho/pen/FHGeK
function scrollFooter(scrollY, heightFooter)
{
console.log(scrollY);
console.log(heightFooter);
if(scrollY >= heightFooter)
{
$('footer').css({
'bottom' : '0px'
});
}
else
{
$('footer').css({
'bottom' : '-' + heightFooter + 'px'
});
}
}
$(window).load(function(){
var windowHeight = $(window).height(),
footerHeight = $('footer').height(),
heightDocument = (windowHeight) + ($('.content').height()) + ($('footer').height()) - 20;
// Definindo o tamanho do elemento pra animar
$('#scroll-animate, #scroll-animate-main').css({
'height' : heightDocument + 'px'
});
// Definindo o tamanho dos elementos header e conteúdo
$('header').css({
'height' : windowHeight + 'px',
'line-height' : windowHeight + 'px'
});
$('.wrapper-parallax').css({
'margin-top' : windowHeight + 'px'
});
scrollFooter(window.scrollY, footerHeight);
// ao dar rolagem
window.onscroll = function(){
var scroll = window.scrollY;
$('#scroll-animate-main').css({
'top' : '-' + scroll + 'px'
});
$('header').css({
'background-position-y' : 50 - (scroll * 100 / heightDocument) + '%'
});
scrollFooter(scroll, footerHeight);
}
});
#scroll-animate
{
overflow: hidden;
}
#scroll-animate-main
{
width: 100%;
left: 0;
position: fixed;
}
#heightPage,
#heightScroll
{
width: 10px;
top: 0;
position: absolute;
z-index: 99;
}
#heightPage
{
left: 0;
}
#heightScroll
{
right: 0;
}
header
{
width: 100%;
height: 100%;
background: url(https://raw.githubusercontent.com/hudsonmarinho/header-and-footer-parallax-effect/master/assets/images/bg-header.jpg) no-repeat 50% 50%;
top: 0;
position: fixed;
z-index: -1;
}
footer
{
width: 100%;
height: 300px;
background: gray;
bottom: -300px;
position: fixed;
z-index: -1;
}
.content
{
height: 1000px;
min-height: 1000px;
background: #ededed;
position: relative;
z-index: 1;
}
.wrapper-parallax {
margin-top: 100%;
margin-bottom: 300px;
box-shadow: 0px 0px 20px rgba(0, 0, 0, 0.5);
}
h1{
width: 100%;
height: 100%;
padding: 0;
margin: 0;
text-transform: uppercase;
text-align: center;
font-family: Helvetica;
font-size: 150px;
color: #fff;
}
header h1{}
.content h1{
line-height: 1000px;
color: #999;
}
footer h1
{
line-height: 300px;
}
header,
footer,
#scroll-animate-main
{
-webkit-transition-property: all;
-moz-transition-property: all;
transition-property: all;
-webkit-transition-duration: 0.4s;
-moz-transition-duration: 0.4s;
transition-duration: 0.4s;
-webkit-transition-timing-function: cubic-bezier(0, 0, 0, 1);
-moz-transition-timing-function: cubic-bezier(0, 0, 0, 1);
transition-timing-function: cubic-bezier(0, 0, 0, 1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="scroll-animate">
<div id="scroll-animate-main">
<div class="wrapper-parallax">
<header>
<h1>Header</h1>
</header>
<section class="content">
<h1>Content</h1>
</section>
<footer>
<h1>Footer</h1>
</footer>
</div>
</div>
</div>