'THREE.js revolving smoke effect works only for one element instead of applying to all elements
I am trying to create a custom post header background for a forum website. But because I always want to make my life more difficult I tried to make it fancy. I also need to link to that script in every post.
When I duplicate the post (or simulate it by just ctrl+a ctrl+c ctrl+v ctrl+v the HTMP code) only the last post gets any smoke effect background. I tried to add the function to the window on init and loop it for every element with the SiffrinSmoke class but still only one smoke is displayed - at the bottom.
HTML:
<div class="SiffrinBody">
<div class="SiffrinHeader">
<div class="SiffrinSmoke">
<!--h1>Siffrin<br>Drauglir</h1-->
<h1 data-heading="Siffrin
Drauglir"></h1>
</div>
<div class="SiffrinCanvasBackground" id="SiffrinCanvasBackground"></div>
<div class="SiffrinCanvasForeground" id="SiffrinCanvasForeground"></div>
<img id="SiffrinHeaderBackground" src="https://i.imgur.com/iYnkBdZ.jpg">
<img id="SiffrinWolf" src="https://i.imgur.com/MBLqG00.png">
</div>
</div>
CSS:
@import url('https://fonts.googleapis.com/css2?family=Arvo:ital@0;1&family=Carter+One&family=Dosis:wght@400;500;600&display=swap');
.SiffrinBody .SiffrinHeader .SiffrinCanvasBackground{
height: 330px;
width: 656px;
position: absolute;
left: inherit;
top: inherit;
z-index: 5;
}
.SiffrinBody .SiffrinHeader .SiffrinCanvasForeground{
height: 330px;
width: 656px;
position: absolute;
left: inherit;
top: inherit;
z-index: 7;
}
.SiffrinBody .SiffrinHeader img{
position: absolute;
height: 330px;
width: 656px;
grid-row: 1;
}
.SiffrinBody .SiffrinHeader .SiffrinSmoke{
width: 328px;
height: 330px;
position: relative;
z-index: 6;
float: right;
display: block;
}
.SiffrinBody .SiffrinHeader .SiffrinSmoke canvas{
position: absolute;
top: 0;
left: 0;
z-index: -4;
}
.SiffrinBody .SiffrinHeader #SiffrinHeaderBackground{
z-index: 4;
}
.SiffrinBody .SiffrinHeader #SiffrinWolf{
z-index: 6;
}
.SiffrinBody .SiffrinHeader h1{
font-family: Carter One;
z-index: 1000;
color: white;
grid-column: 2;
font-size: 50;
margin-top: 32%;
text-align: center;
background: url(https://s3-us-west-2.amazonaws.com/s.cdpn.io/209981/6963bbf342d87b3a2150bd8f59682b89.jpg);
-webkit-background-clip: text;
background-size: cover;
width: 100%;
color: transparent;
font-weight: 900;
display: block;
white-space: pre-wrap !important;
line-height: 1.1em;
text-shadow: 2px 2px 6px rgba(46,146,211,0.8);
}
.SiffrinBody .SiffrinHeader h1::before{
content: attr(data-heading);
position: relative;
left: 0;
top: 0;
width: 100%;
background: linear-gradient(45deg, rgba(255,255,255,0) 45%,rgba(255,255,255,0.8) 50%,rgba(255,255,255,0) 55%,rgba(255,255,255,0) 100%);
-webkit-background-clip: text;
color: transparent;
mix-blend-mode: screen;
animation: SiffrinShine 15s infinite linear;
background-size: 200%;
}
.SiffrinBody .SiffrinHeader #SiffrinName{
z-index: 22;
transform: scale(0.8);
left: 150px;
}
.SiffrinBody .SiffrinHeader{
grid-row: 1;
mask-image: linear-gradient(
to top,
rgba(255, 255, 255, 0) 0,
rgba(255, 255, 255, 1) 15px
);
}
.SiffrinBody .SiffrinHeader > *{
user-select: none;
}
.SiffrinBody{
color: white;
display: grid;
grid-template-rows: 330px auto auto;
grid-template-columns: 1fr;
margin: 0;
padding: 0;
width: 656px;
/*background-image: linear-gradient(#bad4eb, #bad4eb, #a7c8e7, #4789c6, #037ccf);*/
background-image: url("https://i.imgur.com/UqFcRzS.jpg");
background-repeat: repeat;
}
.SiffrinBody *::selection{
background: rgba(225, 0, 255, 0.3);
color: inherit;
}
.SiffrinBody .SiffrinHeader{
/*
z-index: 7;
text-align: center;
font-family: Oleo Script;
margin: auto;
font-size: 30px;
transition-duration: 2s;
transition-timing-function: ease-in-out;
background-image: radial-gradient(#0e111800, #232b3e00, #24212a00, #403b6600);
user-select: none;
*/
}
.SiffrinBody .SiffrinBody:hover .SiffrinHeader{
/*
letter-spacing: 0.35em;
transition-duration: 3s;
transition-timing-function: ease-in-out;
transform: translate(0px, 40px);
*/
}
.SiffrinBody .SiffrinText{
z-index: 7;
margin: 10px 50px;
padding: 20px;
background-color: rgba(0, 0, 0, 0.5);
font-family: Dosis;
font-size: 17px;
margin-bottom: 0;
line-height: 1.235em;
font-weight: 500;
}
.SiffrinBody .SiffrinText::before{
border-top: 3px solid blue;
border-right: 3px solid blue;
}
.SiffrinBody .SiffrinText::after{
border-bottom: 3px solid orange;
border-left: 3px solid orange;
}
.SiffrinBody .SiffrinText p{
padding: 0;
margin-bottom: 1em;
}
.SiffrinBody .SiffrinText p u,
.SiffrinBody .SiffrinText p i,
.SiffrinBody .SiffrinText p q{
font-family: Arvo;
font-size: 16px;
}
.SiffrinBody .SiffrinText p u{
text-decoration: none;
color:rgb(250, 210, 255);
}
.SiffrinBody .SiffrinText p u::before {
content: "“"
}
.SiffrinBody.SiffrinText p u::after {
content: "”"
}
.SiffrinBody .SiffrinText p i{
font-family: Arvo;
color:rgb(250, 210, 255);
font-style: italic;
}
.SiffrinBody .SiffrinText p q{
text-decoration: none;
color:rgb(219, 223, 255);
}
.SiffrinBody .SiffrinText p:last-child{
margin: 0;
}
.SiffrinBody .SiffrinText p:first-child{
margin-top: 0;
}
.SiffrinBody .SiffrinFooter{
display: flex;
align-items: center;
justify-content: center;
padding: 10px;
margin: 0;
}
.SiffrinBody .SiffrinFooter p{
text-align: center;
font-size: 20px;
color: #f8f7fd;
text-align: center;
}
@keyframes SiffrinShine {
0% {background-position: 110%;}
60% {background-position: -90%;}
100% {background-position: -90%;}
}
Import THREE:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r71/three.min.js"></script>
JS:
function initiateSmoke() {
smokeBackgrounds = document.getElementsByClassName('SiffrinSmoke');
clock = new THREE.Clock();
renderW = smokeBackgrounds[0].offsetWidth;
renderH = smokeBackgrounds[0].offsetHeight;
renderer = new THREE.WebGLRenderer({ alpha: true });
renderer.setSize( renderW, renderH );
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera( 75, renderW / renderH , 1, 10000 );
camera.position.z = 1300;
scene.add( camera );
geometry = new THREE.CubeGeometry( 200, 200, 200 );
material = new THREE.MeshLambertMaterial( { color: 0xaa6666, wireframe: false } );
mesh = new THREE.Mesh( geometry, material );
cubeSineDriver = 0;
textGeo = new THREE.PlaneGeometry(300,300);
THREE.ImageUtils.crossOrigin = ''; //Need this to pull in crossdomain images from AWS
light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(-1,0,1);
scene.add(light);
smokeTexture = THREE.ImageUtils.loadTexture('https://s3-us-west-2.amazonaws.com/s.cdpn.io/95637/Smoke-Element.png');
smokeMaterial = new THREE.MeshLambertMaterial({color: 0x333333, opacity: 1, map: smokeTexture, transparent: true});
smokeGeo = new THREE.PlaneGeometry(650,650);
smokeParticles = [];
for (p = 0; p < 36; p++) {
particle = new THREE.Mesh(smokeGeo,smokeMaterial);
particle.position.set(Math.random() * 950 - 500, Math.random() * 620 - 300, Math.random() * 200 - 100);
particle.rotation.z = Math.random() * 360;
particle.scale.set(2,2,2);
scene.add(particle);
smokeParticles.push(particle);
}
for(i = 0; i < smokeBackgrounds.length; i++) {
smokeBackgrounds[i].appendChild( renderer.domElement );
}
}
function animateSmoke() {
// note: three.js includes requestAnimationFrame shim
delta = clock.getDelta();
requestAnimationFrame( animateSmoke );
evolveSmoke();
render();
}
function evolveSmoke() {
var sp = smokeParticles.length;
while(sp--) {
smokeParticles[sp].rotation.z += (delta * 0.2);
}
}
function render() {
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.01;
cubeSineDriver += .01;
mesh.position.z = 100 + (Math.sin(cubeSineDriver) * 500);
renderer.render( scene, camera );
}
window.onload = function Snowfall(){
initiateSmoke();
animateSmoke();
}
Do you know how to apply that same effect as background for every element with that class? Maybe another tool would be better for the job?
EDIT: Solution The Smoke init function accepts the background elements from before and returns relevant objects
function initiateSmoke(smokeBackground) {
[...]
return({
"renderer": renderer,
"scene": scene,
"camera": camera,
"mesh": mesh,
"cubeSineDriver": cubeSineDriver,
"smokeParticles": smokeParticles
})
}
Then in window.onloadI have this loop
smokeBackgrounds = document.getElementsByClassName('SiffrinSmoke');
for(i = 0; i < smokeBackgrounds.length; i++) {
smokeScenes[i] = initiateSmoke(smokeBackgrounds[i]);
smokeBackgrounds[i].appendChild(smokeScenes[i]['renderer'].domElement);
}
animateSmoke();
I also edited the animate functions
function animateSmoke() {
delta = clock.getDelta();
requestAnimationFrame(animateSmoke);
evolveSmoke();
render();
}
function evolveSmoke() {
for(i = 0; i < smokeScenes.length; i++) {
smokeScene = smokeScenes[i];
var sp = smokeScene['smokeParticles'].length;
while(sp--) {
smokeScene['smokeParticles'][sp].rotation.z += (delta * 0.2);
}
}
}
function render() {
for(i = 0; i < smokeScenes.length; i++) {
smokeScene = smokeScenes[i];
smokeScene['mesh'].rotation.x += 0.005;
smokeScene['mesh'].rotation.y += 0.01;
smokeScene['cubeSineDriver'] += .01;
smokeScene['mesh'].position.z = 100 + (Math.sin(smokeScene['cubeSineDriver']) * 500);
smokeScene['renderer'].render( smokeScene['scene'], smokeScene['camera']);
}
}
let smokeScenes = []
let clock = new THREE.Clock();
and that works!
Solution 1:[1]
It's because you're only creating one canvas, and just appending that to each "smokeBackgrounds" in the loop. But each one overwrites the last one, because there's only one canvas (renderer.domElement).
There's a few ways you could solve it:
- Create a loop outside of this code that loops through smokeBackgrounds, and calls initiateSmoke(smokeBackgrounds[i]), and then change that loop towards the end of the method to use the passed-in dom element.
- Clone the canvas itself, and assign each new instance a smokeBackgrounds.
- (Hard) Create multiple threejs scenes within the code, and assign each a position on the page. This way you can stretch the canvas to the full-page, and just move around the smoke positions to be associated with where the HTML elements are. The advantage of this is it's lighter because you won't have multiple canvas's running.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Keith Thomas |