Вы должны вставить приведенный ниже код JS только после того, как DOM будет загружен и готов. document.querySelector('.js-slider')
преобразуется в ноль, что составляет this.el null
при выполнении сценария и следующей строки, т.е.
this.inner = this.el.querySelector('.js-slider__inner')
, чтобы выдать ошибку Cannot read property 'querySelector' of null
// Init classes
const slider = new Slider()
class Slider {
constructor() {
this.bindAll()
this.vert = `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`
this.frag = `
varying vec2 vUv;
uniform sampler2D texture1;
uniform sampler2D texture2;
uniform sampler2D disp;
uniform float dispPower;
uniform float intensity;
uniform vec2 size;
uniform vec2 res;
vec2 backgroundCoverUv( vec2 screenSize, vec2 imageSize, vec2 uv ) {
float screenRatio = screenSize.x / screenSize.y;
float imageRatio = imageSize.x / imageSize.y;
vec2 newSize = screenRatio < imageRatio
? vec2(imageSize.x * (screenSize.y / imageSize.y), screenSize.y)
: vec2(screenSize.x, imageSize.y * (screenSize.x / imageSize.x));
vec2 newOffset = (screenRatio < imageRatio
? vec2((newSize.x - screenSize.x) / 2.0, 0.0)
: vec2(0.0, (newSize.y - screenSize.y) / 2.0)) / newSize;
return uv * screenSize / newSize + newOffset;
}
void main() {
vec2 uv = vUv;
vec4 disp = texture2D(disp, uv);
vec2 dispVec = vec2(disp.x, disp.y);
vec2 distPos1 = uv + (dispVec * intensity * dispPower);
vec2 distPos2 = uv + (dispVec * -(intensity * (1.0 - dispPower)));
vec4 _texture1 = texture2D(texture1, distPos1);
vec4 _texture2 = texture2D(texture2, distPos2);
gl_FragColor = mix(_texture1, _texture2, dispPower);
}
`
this.el = document.querySelector('.js-slider')
this.inner = this.el.querySelector('.js-slider__inner')
this.slides = [...this.el.querySelectorAll('.js-slide')]
this.bullets = [...this.el.querySelectorAll('.js-slider-bullet')]
this.renderer = null
this.scene = null
this.clock = null
this.camera = null
this.images = [
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/bg1.jpg',
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/bg2.jpg',
'https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/bg3.jpg'
]
this.data = {
current: 0,
next: 1,
total: this.images.length - 1,
delta: 0
}
this.state = {
animating: false,
text: false,
initial: true
}
this.textures = null
this.init()
}
bindAll() {
['render', 'nextSlide']
.forEach(fn => this[fn] = this[fn].bind(this))
}
setStyles() {
this.slides.forEach((slide, index) => {
if (index === 0) return
TweenMax.set(slide, { autoAlpha: 0 })
})
this.bullets.forEach((bullet, index) => {
if (index === 0) return
const txt = bullet.querySelector('.js-slider-bullet__text')
const line = bullet.querySelector('.js-slider-bullet__line')
TweenMax.set(txt, {
alpha: 0.25
})
TweenMax.set(line, {
scaleX: 0,
transformOrigin: 'left'
})
})
}
cameraSetup() {
this.camera = new THREE.OrthographicCamera(
this.el.offsetWidth / -2,
this.el.offsetWidth / 2,
this.el.offsetHeight / 2,
this.el.offsetHeight / -2,
1,
1000
)
this.camera.lookAt(this.scene.position)
this.camera.position.z = 1
}
setup() {
this.scene = new THREE.Scene()
this.clock = new THREE.Clock(true)
this.renderer = new THREE.WebGLRenderer({ alpha: true })
this.renderer.setPixelRatio(window.devicePixelRatio)
this.renderer.setSize(this.el.offsetWidth, this.el.offsetHeight)
this.inner.appendChild(this.renderer.domElement)
}
loadTextures() {
const loader = new THREE.TextureLoader()
loader.crossOrigin = ''
this.textures = []
this.images.forEach((image, index) => {
const texture = loader.load(image + '?v=' + Date.now(), this.render)
texture.minFilter = THREE.LinearFilter
texture.generateMipmaps = false
if (index === 0 && this.mat) {
this.mat.uniforms.size.value = [
texture.image.naturalWidth,
texture.image.naturalHeight
]
}
this.textures.push(texture)
})
this.disp = loader.load('https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/rock-_disp.png', this.render)
this.disp.magFilter = this.disp.minFilter = THREE.LinearFilter
this.disp.wrapS = this.disp.wrapT = THREE.RepeatWrapping
}
createMesh() {
this.mat = new THREE.ShaderMaterial( {
uniforms: {
dispPower: { type: 'f', value: 0.0 },
intensity: { type: 'f', value: 0.5 },
res: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
size: { value: new THREE.Vector2(1, 1) },
texture1: { type: 't', value: this.textures[0] },
texture2: { type: 't', value: this.textures[1] },
disp: { type: 't', value: this.disp }
},
transparent: true,
vertexShader: this.vert,
fragmentShader: this.frag
})
const geometry = new THREE.PlaneBufferGeometry(
this.el.offsetWidth,
this.el.offsetHeight,
1
)
const mesh = new THREE.Mesh(geometry, this.mat)
this.scene.add(mesh)
}
transitionNext() {
TweenMax.to(this.mat.uniforms.dispPower, 2.5, {
value: 1,
ease: Expo.easeInOut,
onUpdate: this.render,
onComplete: () => {
this.mat.uniforms.dispPower.value = 0.0
this.changeTexture()
this.render.bind(this)
this.state.animating = false
}
})
const current = this.slides[this.data.current]
const next = this.slides[this.data.next]
const currentImages = current.querySelectorAll('.js-slide__img')
const nextImages = next.querySelectorAll('.js-slide__img')
const currentText = current.querySelectorAll('.js-slider__text-line div')
const nextText = next.querySelectorAll('.js-slider__text-line div')
const currentBullet = this.bullets[this.data.current]
const nextBullet = this.bullets[this.data.next]
const currentBulletTxt = currentBullet.querySelectorAll('.js-slider-bullet__text')
const nextBulletTxt = nextBullet.querySelectorAll('.js-slider-bullet__text')
const currentBulletLine = currentBullet.querySelectorAll('.js-slider-bullet__line')
const nextBulletLine = nextBullet.querySelectorAll('.js-slider-bullet__line')
const tl = new TimelineMax({ paused: true })
if (this.state.initial) {
TweenMax.to('.js-scroll', 1.5, {
yPercent: 100,
alpha: 0,
ease: Power4.easeInOut
})
this.state.initial = false
}
tl
.staggerFromTo(currentImages, 1.5, {
yPercent: 0,
scale: 1
}, {
yPercent: -185,
scaleY: 1.5,
ease: Expo.easeInOut
}, 0.075)
.to(currentBulletTxt, 1.5, {
alpha: 0.25,
ease: Linear.easeNone
}, 0)
.set(currentBulletLine, {
transformOrigin: 'right'
}, 0)
.to(currentBulletLine, 1.5, {
scaleX: 0,
ease: Expo.easeInOut
}, 0)
if (currentText) {
tl
.fromTo(currentText, 2, {
yPercent: 0
}, {
yPercent: -100,
ease: Power4.easeInOut
}, 0)
}
tl
.set(current, {
autoAlpha: 0
})
.set(next, {
autoAlpha: 1
}, 1)
if (nextText) {
tl
.fromTo(nextText, 2, {
yPercent: 100
}, {
yPercent: 0,
ease: Power4.easeOut
}, 1.5)
}
tl
.staggerFromTo(nextImages, 1.5, {
yPercent: 150,
scaleY: 1.5
}, {
yPercent: 0,
scaleY: 1,
ease: Expo.easeInOut
}, 0.075, 1)
.to(nextBulletTxt, 1.5, {
alpha: 1,
ease: Linear.easeNone
}, 1)
.set(nextBulletLine, {
transformOrigin: 'left'
}, 1)
.to(nextBulletLine, 1.5, {
scaleX: 1,
ease: Expo.easeInOut
}, 1)
tl.play()
}
prevSlide() {
}
nextSlide() {
if (this.state.animating) return
this.state.animating = true
this.transitionNext()
this.data.current = this.data.current === this.data.total ? 0 : this.data.current + 1
this.data.next = this.data.current === this.data.total ? 0 : this.data.current + 1
}
changeTexture() {
this.mat.uniforms.texture1.value = this.textures[this.data.current]
this.mat.uniforms.texture2.value = this.textures[this.data.next]
}
listeners() {
window.addEventListener('wheel', this.nextSlide, { passive: true })
}
render() {
this.renderer.render(this.scene, this.camera)
}
init() {
this.setup()
this.cameraSetup()
this.loadTextures()
this.createMesh()
this.setStyles()
this.render()
this.listeners()
}
}
// Toggle active link
const links = document.querySelectorAll('.js-nav a')
links.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault()
links.forEach(other => other.classList.remove('is-active'))
link.classList.add('is-active')
})
})
// Init classes
const slider = new Slider()
html {
font-size: 16px;
}
html, body {
height: 100%;
}
body {
display: flex;
align-items: center;
justify-content: center;
background-color: #111;
font-family: "helvetica neue", helvetica, sans-serif;
overflow: hidden;
}
a {
color: #fff;
text-decoration: none;
}
.scroll {
position: absolute;
bottom: 2rem;
left: 50%;
transform: translateX(-50%);
color: rgba(255, 255, 255, 0.5);
font-family: "font-2";
font-size: calc(0.5rem + 0.35vw);
z-index: 10;
}
.logo {
position: absolute;
top: 2rem;
left: 50%;
transform: translateX(-50%);
padding: 0;
margin: 0;
z-index: 10;
}
.logo img {
display: block;
height: 1rem;
width: auto;
}
ul, li {
list-style: none;
padding: 0;
margin: 0;
}
.nav {
position: absolute;
top: 2rem;
z-index: 10;
}
.nav--left {
left: 1rem;
}
.nav--right {
right: 1rem;
}
.nav ul {
display: flex;
align-items: center;
height: 1rem;
}
.nav li {
display: block;
margin: 0 1rem;
padding: 0;
}
.nav a {
position: relative;
display: flex;
align-items: center;
font-size: calc(0.5rem + 0.35vw);
font-family: "helvetica neue", helvetica, sans-serif;
}
.nav a span {
position: relative;
}
.nav a span:before {
content: "";
position: absolute;
left: 0;
bottom: -0.35rem;
width: 100%;
height: 1px;
background-color: rgba(255, 255, 255, 0.25);
transition: transform 0.75s ease;
transform-origin: right;
transform: scaleX(0);
}
.nav a:hover span:before, .nav a.is-active span:before {
transform: scaleX(1);
transform-origin: left;
}
.vert-text {
position: absolute;
bottom: 2rem;
right: 2rem;
width: 15rem;
display: flex;
align-items: center;
z-index: 10;
}
.vert-text span {
display: block;
color: #fff;
text-transform: uppercase;
line-height: 1.1;
transform: rotate(-90deg) translateY(15rem);
transform-origin: bottom left;
font-size: 1.35rem;
}
.cart-total {
display: block;
height: 2rem;
width: 2rem;
background-color: rgba(255, 255, 255, 0.25);
border-radius: 50%;
text-align: center;
line-height: 2rem;
margin-left: 1rem;
}
.slider {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.slider__text {
position: absolute;
bottom: calc(2rem + 3vw);
left: calc(2rem + 3vw);
z-index: 10;
font-size: calc(1rem + 4vw);
text-transform: uppercase;
transform-origin: top;
line-height: 1.075;
color: #fff;
font-weight: 500;
}
.slider__text-line {
overflow: hidden;
}
.slider__inner {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.slider__nav {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
z-index: 10;
}
.slider-bullet {
display: flex;
align-items: center;
padding: 1rem 0;
}
.slider-bullet__text {
color: #fff;
font-size: 0.65rem;
margin-right: 1rem;
}
.slider-bullet__line {
background-color: #fff;
height: 1px;
width: 1rem;
}
.slider canvas {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.slide {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
}
.slide__content {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.slide__img {
position: relative;
width: 25vw;
height: 70vh;
padding: 0;
margin: 0;
min-width: 12.5rem;
transform-origin: top;
}
.slide__img:first-child {
top: -1.5rem;
}
.slide__img:last-child {
bottom: -1.5rem;
}
.slide__img img {
display: block;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
object-fit: cover;
}
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/2.0.2/TweenMax.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/99/three.min.js"></script>
<nav class="nav nav--left js-nav">
<ul>
<li>
<a href="#"><aspan>Mens</aspan></a>
</li>
<li>
<a href="#"><span>Womens</span></a>
</li>
<li>
<a href="#" class="is-active"><span>Collections</span></a>
</li>
</ul>
</nav>
<figure class="logo">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/logo_copy_copy.svg">
</figure>
<nav class="nav nav--right">
<ul>
<li>
<a href="https://twitter.com/Jesper_Landberg?lang=en" target="_blank"><span>Say hi</span></a>
</li>
<li>
<a href="#">
<span>Cart</span>
<div class="cart-total">0</div>
</a>
</li>
</ul>
</nav>
<div class="slider js-slider">
<div class="slider__inner js-slider__inner"></div>
<div class="slide js-slide">
<div class="slide__content">
<figure class="slide__img js-slide__img">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo1.jpg">
</figure>
<figure class="slide__img js-slide__img">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo2.jpg">
</figure>
</div>
<div class="slider__text js-slider__text">
<div class="slider__text-line js-slider__text-line">
<div>Black is</div>
</div>
<div class="slider__text-line js-slider__text-line">
<div>timeless. Black is</div>
</div>
<div class="slider__text-line js-slider__text-line">
<div>the colour of</div>
</div>
<div class="slider__text-line js-slider__text-line">
<div>Eternity.</div>
</div>
</div>
</div>
<div class="slide js-slide">
<div class="slide__content">
<figure class="slide__img js-slide__img">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo3.jpg">
</figure>
<figure class="slide__img js-slide__img">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo4.jpg">
</figure>
</div>
</div>
<div class="slide js-slide">
<div class="slide__content">
<figure class="slide__img js-slide__img">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo5.jpg">
</figure>
<figure class="slide__img js-slide__img">
<img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/58281/photo6.jpg">
</figure>
</div>
</div>
<nav class="slider__nav js-slider__nav">
<div class="slider-bullet js-slider-bullet">
<span class="slider-bullet__text js-slider-bullet__text">01</span>
<span class="slider-bullet__line js-slider-bullet__line"></span>
</div>
<div class="slider-bullet js-slider-bullet">
<span class="slider-bullet__text js-slider-bullet__text">02</span>
<span class="slider-bullet__line js-slider-bullet__line"></span>
</div>
<div class="slider-bullet js-slider-bullet">
<span class="slider-bullet__text js-slider-bullet__text">03</span>
<span class="slider-bullet__line js-slider-bullet__line"></span>
</div>
</nav>
<div class="scroll js-scroll">Scroll</div>
</div>
<div class="vert-text">
<span>
Wings+Horns<br>
X Kyoto Black
</span>
</div>