
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="styles/main.css" />
<title>CanyonTravel</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Lato";
font-size: 95%;
color: #0d0e1f;
background: #fefaff;
}
p {
line-height: 28px;
letter-spacing: 0.75px;
}
a {
color: inherit;
text-decoration: none;
}
.main {
max-width: 1620px;
height: 100vh;
min-height: 600px;
margin: auto;
display: flex;
flex-flow: row;
gap: 2em;
justify-content: space-around;
align-items: center;
}
.container {
position: relative;
min-height: 600px;
max-height: 750px;
height: inherit;
flex: 1 0 75%;
margin: auto;
border-radius: 1.5em;
overflow: hidden;
box-shadow: -42px 32px 65px -2px #1c1c1d63;
z-index: 1;
}
.container.mini {
flex: 1 0 20%;
z-index: 0;
}
.custom-component {
color: #fefaff;
font-size: inherit;
width: 100%;
}
.custom-component .intro {
font-size: 1.5rem;
font-weight: 300;
margin-bottom: 8px;
}
.custom-component .title {
font-size: 3rem;
font-weight: 800;
text-transform: uppercase;
line-height: 1;
margin-bottom: 16px;
}
.custom-component .context {
margin-bottom: 32px;
}
.custom-component .btn {
display: inline-block;
padding: 1em 2em;
background-color: #f7de52;
color: #0d0e1f;
text-transform: uppercase;
font-weight: 800;
}
.custom-component.normal .intro {
font-size: 1.75rem;
}
.custom-component.normal .title {
font-size: 5rem;
}
.slider {
display: flex;
flex-flow: column nowrap;
position: relative;
width: 100%;
height: 100%;
overflow: hidden;
scroll-behavior: smooth;
overscroll-behavior: contain;
-ms-overflow-style: none;
scrollbar-width: none;
}
.slider::-webkit-scrollbar {
display: none;
}
.slider.horizontal {
flex-flow: row nowrap;
}
.slider .slide {
flex: 1 0 100%;
position: relative;
}
.slider .slide::before {
content: "";
position: absolute;
background: linear-gradient(0deg, #0d0e1f -10%, transparent 80%);
width: 100%;
height: 100%;
z-index: 0;
}
.slider .slide .background {
position: absolute;
display: block;
width: 100%;
height: 100%;
overflow: hidden;
z-index: -1;
top: 0;
left: 0;
}
.slider .slide .background .background-img {
position: absolute;
width: auto;
height: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.slider .slide .content {
position: relative;
width: auto;
height: 100%;
display: flex;
align-items: flex-end;
padding: 2em;
z-index: 1;
}
.slider .slide .content .component {
position: relative;
opacity: 0;
transform: translate(0, 0);
transition: transform 0.3s linear, opacity 0.5s ease;
}
.slider .slide.active .content .component {
transition: transform 0.3s linear, opacity 2s ease;
opacity: 1;
transform: translate(0, 0%);
}
.slider.normal .slide .background .background-img {
width: 120%;
height: auto;
}
.slider.normal .slide .content {
position: absolute;
display: block;
padding: 0;
height: 100%;
width: 50%;
}
.slider.normal .slide .content .component {
width: 65%;
top: 50%;
left: 50%;
transform: translate(-200%, -50%);
transition: transform 0.5s linear, opacity 2s ease;
}
.slider.normal .slide.active::before {
opacity: 0;
transition: transform 0.3s linear, opacity 1s ease;
}
.slider.normal .slide.active .content .component {
transform: translate(-50%, -50%);
}
.slider.normal .slide.active::before {
opacity: 1;
}
.control {
position: absolute;
bottom: 0;
z-index: 0;
padding: 2em;
right: 0;
z-index: 1;
}
.control .control-button {
position: relative;
display: inline-block;
padding: 0.25em;
background: none;
width: 24px;
height: 24px;
border: none;
outline: none;
opacity: 0.25;
transition: opacity 0.3s ease;
}
.control .control-button.next {
margin-left: 0.5em;
}
.control .control-button svg {
position: relative;
overflow: visible;
width: 100%;
}
.control .control-button svg #path_arrow_prev, .control .control-button svg #path_arrow_next {
stroke: rgba(254, 250, 255, 0.75);
stroke-width: 1.5px;
transition: stroke 0.3s ease;
}
.control .control-button.disabled .hover {
display: none;
}
.control .control-button:not(.disabled) {
cursor: pointer;
opacity: 0.75;
}
.control .control-button:not(.disabled) .hover {
position: absolute;
top: 50%;
left: 50%;
border-radius: 50%;
width: 2.5em;
height: 2.5em;
background: rgba(254, 250, 255, 0.75);
transform-origin: center center;
transform: translate(-50%, -50%) scale(0, 0);
transition: transform 0.3s ease;
}
.control .control-button:not(.disabled):hover.next #path_arrow_next {
stroke: rgba(13, 14, 31, 0.75);
}
.control .control-button:not(.disabled):hover.prev #path_arrow_prev {
stroke: rgba(13, 14, 31, 0.75);
}
.control .control-button:not(.disabled):hover .hover {
transform: translate(-50%, -50%) scale(1, 1);
}
.control.normal {
padding: 0 3em 3em 0;
}
.control.normal .control-button {
width: 32px;
height: 32px;
}
.control.normal .control-button.next {
margin-left: 0.75em;
}
.control.normal .control-button:not(.disabled) .hover {
width: 3em;
height: 3em;
}
.indicator {
position: absolute;
display: none;
right: 3em;
top: 50%;
height: 40%;
transform: translateY(-50%);
}
.indicator .line {
position: relative;
width: 2px;
height: 100%;
background: rgba(254, 250, 255, 0.5);
}
.indicator .line .bar {
position: absolute;
background: #fefaff;
top: 0;
width: 100%;
transition: transform 0.5s ease;
}
.indicator .line .bar .number {
position: absolute;
color: #fefaff;
top: 50%;
transform: translateY(-50%);
right: 1em;
font-size: 1.15rem;
opacity: 0;
transition: opacity 0.5s ease;
pointer-events: none;
}
.indicator.horizontal {
top: unset;
right: unset;
left: 0;
bottom: 0;
height: auto;
width: 100%;
transform: unset;
}
.indicator.horizontal .line {
width: 100%;
height: 2px;
}
.indicator.horizontal .line .bar {
left: 0;
top: unset;
transform: translate(0, 0);
height: 100%;
}
.indicator.horizontal .line .bar .number {
display: none;
}
</style>
</head>
<body>
<div class="main">
<div class="container">
<div class="slider normal" data-scroll-orientation data-delay data-indicator-delay="3000" data-name="normal">
<div class="slide">
<div class="content">
<div class="custom-component component normal">
<h3 class="intro">Discover</h3>
<h2 class="title">Tuweep Overlook</h2>
<p class="context">
Toroweap, also known as Tuweep Overlooks offers a dramatic
view of the Colorado coming down through the canyon
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1556379302-ced3a2e8d73e?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNjU2Ng&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
<div class="slide">
<div class="content">
<div class="custom-component component normal">
<h3 class="intro">Discover</h3>
<h2 class="title">Desert View</h2>
<p class="context">
Desert View is quite unique compared to other viewpoints as it
provides a view of the open canyon, and the bend of the
Colorado River making a turn from southbound to westbound
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1626154515999-da11879984e6?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNjc2Mw&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
<div class="slide">
<div class="content">
<div class="custom-component component normal">
<h3 class="intro">Discover</h3>
<h2 class="title">Plateau Point</h2>
<p class="context">
Plateau Point is not reached by car, but rather by foot – 10
miles (out and back) along the Bright Angel Trail to be exact
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1520810760276-e2f31dee978f?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNjk0MA&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
<div class="slide">
<div class="content">
<div class="custom-component component normal">
<h3 class="intro">Discover</h3>
<h2 class="title">Shoshone Point</h2>
<p class="context">
Shoshone Point is a special viewpoint because very few people
visit as it is not really advertised on any park maps
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1599952963953-015a71bfeea3?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNzM1Ng&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
</div>
<div class="indicator normal" data-for="normal">
<div class="line"></div>
</div>
<div class="control normal" data-for="normal">
<button class="control-button prev">
<span class="hover"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="20.75" height="11.692" viewBox="0 0 20.75 11.692">
<path id="path_arrow_prev" data-name="Path 308" d="M175.9,229.889H165.288V235.3l-9.9-5.407,9.9-5.407v2.408" transform="translate(-155.149 -224.043)" fill="none" stroke="#2e2e2e" stroke-linejoin="bevel" stroke-width="1" fill-rule="evenodd" />
</svg>
</button>
<button class="control-button next">
<span class="hover"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="20.75" height="11.692" viewBox="0 0 20.75 11.692">
<path id="path_arrow_next" data-name="Path 309" d="M175.9,229.889H165.288V235.3l-9.9-5.407,9.9-5.407v2.408" transform="translate(175.899 235.735) rotate(180)" fill="none" stroke="#2e2e2e" stroke-linejoin="bevel" stroke-width="1" fill-rule="evenodd" />
</svg>
</button>
</div>
</div>
<div class="container mini">
<div class="slider" data-scroll-orientation="horizontal" data-delay data-name="mini">
<div class="slide">
<div class="content">
<div class="custom-component component">
<h3 class="intro">Discover</h3>
<h2 class="title">Tuweep Overlook</h2>
<p class="context">
Toroweap, also known as Tuweep Overlooks offers a dramatic
view of the Colorado coming down through the canyon
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1556379302-ced3a2e8d73e?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNjU2Ng&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
<div class="slide">
<div class="content">
<div class="custom-component component">
<h3 class="intro">Discover</h3>
<h2 class="title">Desert View</h2>
<p class="context">
Desert View is quite unique compared to other viewpoints as it
provides a view of the open canyon, and the bend of the
Colorado River making a turn from southbound to westbound.
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1626154515999-da11879984e6?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNjc2Mw&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
<div class="slide">
<div class="content">
<div class="custom-component component">
<h3 class="intro">Discover</h3>
<h2 class="title">Plateau Point</h2>
<p class="context">
Plateau Point is not reached by car, but rather by foot – 10
miles (out and back) along the Bright Angel Trail to be exact.
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1520810760276-e2f31dee978f?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNjk0MA&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
<div class="slide">
<div class="content">
<div class="custom-component component">
<h3 class="intro">Discover</h3>
<h2 class="title">Shoshone Point</h2>
<p class="context">
Shoshone Point is a special viewpoint because very few people
visit as it is not really advertised on any park maps.
</p>
<a href="" class="btn" role="button">Find out more</a>
</div>
</div>
<div class="background">
<img src="https://images.unsplash.com/photo-1599952963953-015a71bfeea3?crop=entropy&cs=srgb&fm=jpg&ixid=MnwxNDU4OXwwfDF8cmFuZG9tfHx8fHx8fHx8MTYzMjkwNzM1Ng&ixlib=rb-1.2.1&q=85" alt="" class="background-img" />
</div>
</div>
</div>
<div class="indicator" data-for="mini">
<div class="line">
<!-- <span class="bar"></span> -->
</div>
</div>
<div class="control" data-for="mini">
<button class="control-button prev">
<span class="hover"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="20.75" height="11.692" viewBox="0 0 20.75 11.692">
<path id="path_arrow_prev" data-name="Path 308" d="M175.9,229.889H165.288V235.3l-9.9-5.407,9.9-5.407v2.408" transform="translate(-155.149 -224.043)" fill="none" stroke="#2e2e2e" stroke-linejoin="bevel" stroke-width="1" fill-rule="evenodd" />
</svg>
</button>
<button class="control-button next">
<span class="hover"></span>
<svg xmlns="http://www.w3.org/2000/svg" width="20.75" height="11.692" viewBox="0 0 20.75 11.692">
<path id="path_arrow_next" data-name="Path 309" d="M175.9,229.889H165.288V235.3l-9.9-5.407,9.9-5.407v2.408" transform="translate(175.899 235.735) rotate(180)" fill="none" stroke="#2e2e2e" stroke-linejoin="bevel" stroke-width="1" fill-rule="evenodd" />
</svg>
</button>
</div>
</div>
</div>
<script src="utils.js"></script>
<script>
"use strict";
class Slider {
constructor(
slider,
indicator,
buttons,
scrollOrientation = "",
delay = null,
indDelay = null
) {
this.slider = slider;
this.indicators = { indicator };
this.buttons = buttons;
this.slides = [];
this.components = [];
this.index = 0;
this.timeoutId = undefined;
this.scrollVal = { start: 0, end: 0 };
this.staticProperties = {
height: 0,
width: 0,
length: 0,
scrollOrientation: scrollOrientation || "vertical",
delay: delay || 0,
indDelay: indDelay || 1000
};
}
setup() {
this.prepare();
const slider = this.slider;
slider.addEventListener("wheel", (e) => this.handleWheel(e));
slider.addEventListener("touchstart", (e) => this.handleTouchStart(e));
slider.addEventListener("touchend", (e) => this.handleTouchEnd(e));
}
prepare() {
this.slides = Array.from(this.slider.children);
const slidesArr = this.slides;
const slider = this.slider;
const props = this.staticProperties;
let foundContent, foundComponent;
if (!slidesArr.length) return;
props.height = this.slider.clientHeight;
props.width = this.slider.clientWidth;
props.length = this.slides.length;
props.scrollOrientation === "horizontal" &&
slider.classList.add("horizontal");
slidesArr[this.index].classList.add("active");
if (slidesArr.length)
slidesArr.forEach((slide) => {
if (
(foundContent = Array.from(slide.children).find((content) =>
content.classList.contains("content")
))
)
if (
(foundComponent = Array.from(
foundContent.children
).find((component) => component.classList.contains("component")))
)
this.components = [...this.components, foundComponent];
});
this.setIndicator();
this.setButtons();
}
setButtons() {
if (!this.buttons) return;
const buttons = Array.from(this.buttons.children);
buttons.forEach((btn) => {
btn.addEventListener("mouseenter", (e) => this.buttonEffect(e));
btn.addEventListener("mouseleave", (e) => this.buttonEffect(e));
btn.classList.contains("prev") &&
btn.addEventListener("click", (e) =>
this.buttonOnclick(e, true, false)
);
btn.classList.contains("next") &&
btn.addEventListener("click", (e) =>
this.buttonOnclick(e, false, true)
);
});
this.enableButton();
}
enableButton() {
const buttons = Array.from(this.buttons.children);
for (let btn of buttons) {
btn.removeAttribute("disabled");
btn.classList.remove("disabled");
if (btn.classList.contains("prev"))
if (this.index === 0) {
btn.setAttribute("disabled", true);
btn.classList.add("disabled");
}
if (btn.classList.contains("next"))
if (this.index === this.staticProperties.length - 1) {
btn.setAttribute("disabled", true);
btn.classList.add("disabled");
}
}
}
buttonEffect(e) {
if (e.type === "mouseenter" || e.type === "mouseleave") {
const hover = Array.from(e.target.children).find((element) =>
element.classList.contains("hover")
);
let x = (e.offsetX / e.target.clientHeight) * 100;
let y = (e.offsetY / e.target.clientHeight) * 100;
x = x > 100 ? 100 : x < 0 ? 0 : x;
y = y > 100 ? 100 : y < 0 ? 0 : y;
hover.style.transformOrigin = `${x}% ${y}%`;
}
}
buttonOnclick(e, prev, next) {
let i = this.index;
if (e.type === "click") {
if (typeof prev !== "boolean" || typeof next !== "boolean") return;
if (prev) i -= 1;
if (next) i += 1;
this.setIndex(i);
this.scroll();
}
}
setIndicator() {
if (!this.indicators.indicator) return;
const { scrollOrientation: orientation, length } = this.staticProperties;
const ind = this.indicators;
const { indicator } = ind;
const bar = document.createElement("span");
const number = document.createElement("span");
const horizontal = orientation === "horizontal";
indicator.style.display = "block";
horizontal && indicator.classList.add("horizontal");
bar.className = "bar";
bar.style[horizontal ? "width" : "height"] = `${100 / length}%`;
bar.addEventListener("mouseenter", () => this.showIndicator());
bar.addEventListener("mouseleave", () => this.showIndicator());
number.className = "number";
number.innerText = this.index < 9 ? `0${this.index + 1}` : this.index + 1;
ind.bar = bar;
ind.number = number;
bar.appendChild(number);
indicator.children[0].appendChild(bar);
this.showIndicator();
}
showIndicator() {
this.indicators.number.style.opacity = 0.75;
clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => {
this.indicators.number.style.opacity = 0;
}, this.staticProperties.indDelay);
}
handleWheel(e) {
let i = this.index;
if (e.type === "wheel") {
if (
(i <= 0 && e.deltaY < 0) ||
(i >= this.staticProperties.length - 1 && e.deltaY > 0)
)
return;
this.scroll();
if (e.deltaY > 0) i += 1;
if (e.deltaY < 0) i -= 1;
this.setIndex(i);
}
}
handleTouchStart(e) {
const sv = this.scrollVal;
if (e.type === "touchstart") {
if (this.staticProperties.scrollOrientation === "horizontal")
sv.start = e.touches[0].clientX;
else sv.start = e.touches[0].clientY;
}
}
handleTouchEnd(e) {
const sv = this.scrollVal;
let i = this.index;
if (e.type === "touchend") {
if (this.staticProperties.scrollOrientation === "horizontal")
sv.end = e.changedTouches[0].clientX;
else sv.end = e.changedTouches[0].clientY;
if (
(i <= 0 && sv.end > sv.start) ||
(i >= this.staticProperties.length - 1 && sv.end < sv.start)
)
return;
this.scroll();
const remainder = Math.abs(sv.start - sv.end);
if (remainder > 100) {
if (sv.end > sv.start) i -= 1;
if (sv.end < sv.start) i += 1;
}
this.setIndex(i);
}
}
scroll() {
const {
height,
width,
scrollOrientation: orientation,
length,
delay
} = this.staticProperties;
const ind = this.indicators;
const horizontal = orientation === "horizontal";
let val;
this.slides.forEach((slide) => slide.classList.remove("active"));
this.timeoutId && clearInterval(this.timeoutId);
this.timeoutId = setTimeout(() => {
horizontal
? this.slider.scrollTo(width * this.index, 0)
: this.slider.scrollTo(0, height * this.index);
this.slides[this.index].classList.add("active");
if (ind.indicator) {
val = 100 * this.index;
ind.bar.style.transform = `translate(${
horizontal ? `${val}%, 0` : `0, ${val}%`
})`;
if (ind.number) {
val = this.index;
val = val >= length ? length : this.index + 1;
ind.number.innerText = val >= 10 ? val : `0${val}`;
this.showIndicator();
this.enableButton();
}
}
}, delay);
}
setIndex(index) {
const len = this.staticProperties.length;
let i = index;
if (i < 0) i = 0;
if (i > len - 1) i = len - 1;
this.index = i;
}
}
const sliders = Array.from(document.querySelectorAll(".slider"));
const indicator = Array.from(document.querySelectorAll(".indicator"));
const buttons = Array.from(document.querySelectorAll(".control"));
sliders.forEach((slider) => {
const {
scrollOrientation: orientation,
delay,
name,
indicatorDelay: indDelay
} = slider.dataset;
const btn = buttons.find((b) => b.dataset.for === name);
const ind = indicator.find((i) => i.dataset.for === name);
const s = new Slider(slider, ind, btn, orientation, delay, indDelay);
s.setup();
});
</script>
</body>
</html>