Skip to content

Instantly share code, notes, and snippets.

@jesperlandberg
Created May 4, 2021 11:03
Show Gist options
  • Save jesperlandberg/6a0891308ba15afacb0649d4f87cc97c to your computer and use it in GitHub Desktop.
Save jesperlandberg/6a0891308ba15afacb0649d4f87cc97c to your computer and use it in GitHub Desktop.
import gsap from 'gsap'
import { Renderer, Camera, Transform, Post, Vec2 } from '@/lib/ogl'
import { evt, u, store } from '@/app/index'
import Pool from './Pool'
import { Bg } from './components'
import fragment from './shaders/pass/frag.frag'
const { bounds, dom, flags } = store
const { qs } = u
export default new (class {
constructor() {
const { ww, wh } = bounds
this.renderer = new Renderer({
dpr: gsap.utils.clamp(1, 1.5, window.devicePixelRatio),
alpha: true,
premultipliedAlpha: true,
})
this.renderer.setSize(ww, wh)
this.gl = this.renderer.gl
this.gl.canvas.classList.add('gl', 'js-gl')
this.gl.clearColor(0, 0, 0, 0)
this.camera = new Camera(this.gl, {
fov: 45,
aspect: ww / wh,
near: 0.1,
far: 100
})
this.camera.position.z = 50
this.post = new Post(this.gl)
this.res = { value: new Vec2() }
this.post.resize()
this.res.value.set(this.gl.canvas.width, this.gl.canvas.height)
this.pass = this.post.addPass({
fragment,
uniforms: {
u_pinch: { value: 0 },
u_res: this.res,
}
})
this.scene = new Transform()
this.time = 0
this.lastScroll = 0
this.offsetScroll = 0
}
init() {
this.initial = true
this.scene.addChild(Pool.planesGroup)
this.scene.addChild(Pool.gridGroup)
//this.bg = new Bg()
//this.bg.init()
dom.body.appendChild(this.gl.canvas)
this.addEvents()
}
addEvents() {
evt.on('resize:on-reset', this.resize)
}
run = ({ scroll, diff }) => {
this.planes = Pool.planesGroup.children
this.scroll = scroll
this.time += 1
this.planes.length
&& this.transformPlanes(this.scroll, diff)
this.post.render({
scene: this.scene,
camera: this.camera
})
this.lastScroll = this.scroll
}
transformPlanes(scroll = this.scroll, diff = 0) {
if (flags.transition) return
this.planes.forEach((p) => {
if (!p.program
|| p.static
) return
const visible = this.visible(p.bounds, scroll)
if (visible || this.resizing) {
p.out && (p.out = false)
p.vid && !p.vid.active && (p.vid.play(), p.vid.active = true)
p.onRaf(scroll, diff)
} else if (!p.out) {
p.out = true
p.vid && p.vid.active && (p.vid.pause(), p.vid.active = false)
p.onRaf(scroll, diff)
}
})
}
visible({ start, end }, current) {
return current > start && current <= end
}
resize = () => {
const { ww, wh } = bounds
this.resizing = true
this.camera.updateMatrixWorld()
this.renderer.setSize(ww, wh)
Pool.resize()
this.transformPlanes()
this.resizing = false
}
removeEvents() {
evt.off('tick', this.run)
evt.off('resize:on-reset', this.resize)
}
destroy = () => {
this.removeEvents()
Pool.removePlanes()
}
})()
import gsap from 'gsap'
import { store, u, p } from '@/app/index'
import { Transform } from '@/lib/ogl'
import Core from './Core'
const { dom, bounds } = store
const { qs, rect } = u
export default class extends Transform {
init(args = {}) {
this.args = args
this.el = this.args.el
this.pid = this.setPid()
this.name = this.args.name
this.progress = {
x: 0,
y: 0,
}
this.static = false
this.out = true
this.setBounds()
}
update(parent = dom.body) {
this.el = qs(`[data-gl-id="${this.name}"]`, parent)
this.pid = this.setPid()
this.resize()
}
setPid(el = this.el) {
return el.closest('[data-id]')
&& el.closest('[data-id]').dataset.id
}
setBounds() {
const { wh } = bounds
const {
left, right,
top, bottom,
width, height
} = rect(this.el)
this.bounds = {
left, right, width, height, top,
start: top - wh,
end: bottom,
}
this.updateSize()
this.updateY()
this.updateX()
}
resize() {
if (!this.visible) return
this.setBounds()
this.updateX(0)
}
calculateUnitSize(distance = this.position.z) {
const vFov = (Core.camera.fov * Math.PI) / 180
const height = 2 * Math.tan(vFov / 2) * distance
const width = height * Core.camera.aspect
return {
width,
height,
}
}
updateSize() {
this.camUnit = this.calculateUnitSize(
Core.camera.position.z - this.position.z,
)
const { ww, wh } = bounds
const x = this.bounds.width / ww
const y = this.bounds.height / wh
if (!x || !y) return
this.scale.x = this.camUnit.width * x
this.scale.y = this.camUnit.height * y
}
updateY(y = 0) {
const { wh } = bounds
const { top } = this.bounds
this.position.y = this.camUnit.height / 2 - this.scale.y / 2
this.position.y -= ((top - y) / wh) * this.camUnit.height
}
updateX(x = 0) {
const { ww } = bounds
const { left } = this.bounds
this.position.x = -(this.camUnit.width / 2) + this.scale.x / 2
this.position.x += ((left + x) / ww) * this.camUnit.width
}
getSize(bounding, oX = 0, oY = 0) {
this.camUnit = this.calculateUnitSize(
Core.camera.position.z - this.position.z,
)
let scale = {}, pX, pY
const { ww, wh } = bounds
const { width, height, top, left } = bounding
const x = width / ww
const y = height / wh
scale.x = this.camUnit.width * x
scale.y = this.camUnit.height * y
pY = this.camUnit.height / 2 - scale.y / 2
pY -= ((top - oY) / wh) * this.camUnit.height
pX = -(this.camUnit.width / 2) + scale.x / 2
pX += ((left + oX) / ww) * this.camUnit.width
return {
scale, pX, pY
}
}
onRaf(y = 0, diff = 0) {
!this.static && this.updateY(y, diff)
}
destroy() {
this.parent && this.parent.removeChild(this)
this.visible = this.static = false
}
}
import Core from './Core'
import Elem from './Elem'
import Pool from './Pool'
export {
Core,
Pool,
Elem,
}
import {
Mesh,
Plane,
Program,
Texture,
Vec2
} from '@/lib/ogl'
import gsap from 'gsap'
import Core from '../Core'
import Elem from '../Elem'
import Pool from '../Pool'
import vertex from '@/gl/shaders/plane/vert.vert'
import fragment from '@/gl/shaders/plane/frag.frag'
import { evt, store, u, p } from '@/app/index'
const { bounds, dom, flags } = store
const { qs, rect } = u
export default class extends Elem {
init(args) {
const { gl } = Core
super.init(args)
this.geometry = new Plane(gl, {widthSegments: 25, heightSegments: 25})
this.texture = new Texture(gl, {
generateMipmaps: false,
minFilter: gl.LINEAR,
})
const src = this.el.dataset.src || this.el.dataset.glSrc
if (!src) return
const type = src.split('.').pop()
if (type !== 'mp4') {
const img = new Image()
img.crossOrigin = '*'
img.src = src
img.decode().then(() => {
const { u_size } = this.program.uniforms
this.texture.image = img
u_size.value.x = img.naturalWidth
u_size.value.y = img.naturalHeight
})
} else {
this.vid = document.createElement('video')
this.vid.src = src
this.vid.loop = true
this.vid.muted = true
this.vid.play()
this.vid.oncanplay = () => {
const { u_size } = this.program.uniforms
u_size.value.x = this.vid.videoWidth
u_size.value.y = this.vid.videoHeight
}
}
this.program = new Program(gl, {
vertex,
fragment,
uniforms: {
u_texture: { value: this.texture },
u_toRes: { value: new Vec2(0, 0) },
u_toPos: { value: new Vec2(0, 0) },
u_res: { value: new Vec2(this.bounds.width, this.bounds.height) },
u_pos: { value: new Vec2(this.position.x, this.position.y) },
u_offset: { value: new Vec2(this.camUnit.width, this.camUnit.height) },
u_size: { value: new Vec2(1, 1) },
u_progress: { value: 0 },
u_time: { value: 0 },
u_alpha: { value: 1 },
},
cullFace: null,
depthTest: false
})
this.mesh = new Mesh(gl, {
geometry: this.geometry,
program: this.program,
})
this.addChild(this.mesh)
Pool.planesGroup.addChild(this)
Pool.planes[this.name] = this
this.onAdd()
}
onAdd() {
}
updateVid() {
if (this.vid.readyState >= this.vid.HAVE_ENOUGH_DATA) {
!this.texture.image && (this.texture.image = this.vid)
this.texture.needsUpdate = true
}
}
updateY(y = 0, diff = 0) {
super.updateY(y)
this.program && (this.program.uniforms.u_time.value = Core.time)
this.vid && this.updateVid()
}
transition = (to) => {
const { u_progress, u_toPos, u_toRes, u_pos } = this.program.uniforms
const nBounds = rect(qs('.js-gl-hero', to))
const nSize = this.getSize(
nBounds,
0,
0
)
u_toPos.value.x = nSize.pX
u_toPos.value.y = nSize.pY
u_toRes.value.x = nBounds.width
u_toRes.value.y = nBounds.height
gsap.timeline()
.set(u_pos.value, {
x: this.position.x,
y: this.position.y,
})
.to(u_progress, {
value: 1,
duration: 1.25,
ease: 'power3.inOut'
}, 0)
.add(() => {
this.update()
this.keep = false
this.static = false
})
}
resize() {
super.resize()
const { u_res, u_pos, u_progress } = this.program.uniforms
const { width, height } = this.bounds
const { x, y } = this.position
u_progress.value = 0
u_res.value.x = width
u_res.value.y = height
u_pos.value.x = x
u_pos.value.y = y
}
destroy() {
super.destroy()
this.mesh.renderOrder = 1
this.out = true
this.animated = false
}
}
import { Transform } from '@/lib/ogl'
import gsap from 'gsap'
import Elem from './Elem'
import * as Component from './components'
import { store, u, evt } from '@/app/index'
const { bounds, flags } = store
const { qs, qsa } = u
export default new (class {
constructor() {
this.planes = {}
this.planesGroup = new Transform()
this.gridGroup = new Transform()
evt.on('gl:removePlanes', this.removePlanes)
}
fromDom(el, parent) {
const name = el.dataset.glId
const component = el.dataset.glComponent
const plane = this.planes[name]
if (plane && !plane.keep) {
this.add(plane, parent)
} else if (!plane) {
if (component) {
new Component[component]().init({ el, name })
} else {
new Component['Plane']().init({ el, name })
}
}
}
addPlanes({ el, elems }) {
elems && elems.forEach(elem => this.fromDom(elem, el))
}
add(p, parent) {
if (p.visible) return
this.planesGroup.addChild(p)
p.visible = true
p.program.uniforms.u_alpha.value = 1
p.update(parent)
p.onAdd && p.onAdd()
}
resize(el = null) {
for (const name in this.planes) this.planes[name].resize(el)
}
setStatic() {
this.planesGroup.children.forEach(
(p) => (p.static = true),
)
}
removePlanes = (pid) => {
for (const name in this.planes) this.remove(this.planes[name], pid)
}
removePlane(plane) {
(!plane.out && flags.case) ? gsap.to(plane.program.uniforms.u_alpha, {
value: 0,
duration: .5,
ease: 'power1',
onComplete: () => plane.destroy()
}) : plane.destroy()
}
remove(plane, pid) {
!pid
? this.removePlane(plane)
: (!plane.keep && (pid === plane.pid) && this.removePlane(plane))
}
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment