Hi, I’m new to Three.js and coding in general, tbh. I want to make these flowers responsive so that when you open the page on a phone, the flowers are still positioned above the sack. Should I use media queries for that?
This is my code:
html: <!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="logo.png"/>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400&display=swap" rel="stylesheet">
<title>AgroLab</title>
<link rel="stylesheet" href="novedades.css">
</head>
<body>
<script type="module" src="novedades.js"></script>
<div class="novedades-grid">
<div class="noticia" onclick="openPopup('La Semana AgroLab 2023 es un evento que busca generar conversaciones interdisciplinares y diálogos de saberes en torno al alimento, sus posibilidades y relaciones desde la siembra hasta la mesa. Este año será la primera versión del único espacio de la universidad en el que se abordan los sistemas alimentarios desde esta perspectiva holística. A partir del alimento podemos abordar asuntos de: agricultura, tecnología, energía, memoria y territorio, economía, gastronomía, diseño, arquitectura, urbanismo, ecología, derecho, gobernanza y muchos más. Nos proponemos poner atención, no solo a estos aspectos aislados, sino sobre todo a sus intersecciones.', 'novedades1.png')">
<div class="bolsa flip-container">
<div class="card">
<div class="front">
<img src="novedades1.png" alt="Noticia 1">
</div>
<div class="back">
<div class="titulo-flip">Semana AgroLab</div>
</div>
</div>
</div>
</div>
<div class="noticia" onclick="openPopup('Nuestro equipo participara en esta iniciativa de la Universidad de Wageningen. El reto es: We challenge you to develop an urban farming site that significantly improves the quality of life of local residents in one of the most diverse lower-income neighborhoods of Washington D.C', 'novedades2.png')">
<div class="bolsa flip-container">
<div class="card">
<div class="front">
<img src="novedades2.png" alt="Noticia 2">
</div>
<div class="back">
<div class="titulo-flip">Urban Greenhouse Challenge</div>
</div>
</div>
</div>
</div>
<div class="noticia" onclick="openPopup('1. Método tradicional: Se trata de la siembra en materas. La persona debe estar pendiente día a día del riego y el cuidado del cultivo. 2. Hidroponía: Con canales que permiten la acumulación de agua se adelanta un riego programado, que le da al cultivo de plantas soluciones minerales sin el uso de tierra. Así se garantiza que no aparezcan insectos ni plagas. 3. Acuaponía: En esta técnica se utilizan sensores que permiten la recirculación y oxigenación del agua. También se incluyen peces que generan nitritos/nitratos para alimentar las plantas, que luego purifican el agua y devuelven la energía a los peces. Este método permite el ahorro del 90% de los recursos hídricos. 4. Farmbot: Se trata de una máquina de agricultura robotizada con rieles y automatizaciones para la siembra. Permite el control de malezas, riego de agua automático, informe de los cultivos que ya se pueden cosechar y una menor intervención del ser humano. Aunque estos son métodos conocidos, en Los Andes se busca darle una mirada desde distintas disciplinas para conocer sus implicaciones. Por eso, a través del diseño se distribuyó el espacio de las huertas de tal manera que cualquier profesional pueda interactuar y aportar nuevos conocimientos.', 'novedades3.jpg')">
<div class="bolsa flip-container">
<div class="card">
<div class="front">
<img src="novedades3.jpg" alt="Noticia 3">
</div>
<div class="back">
<div class="titulo-flip">Alternativas para cultivar en la ciudad</div>
</div>
</div>
</div>
</div>
<div class="noticia" onclick="openPopup('Te invitamos a conocer un poco más de nuestra variedad de talleres diseñados para conectar con la naturaleza, entendiendo el impacto que puede tener en nuestra cotidianidad y en nuestras dinámicas alimentarias en las ciudades.', 'novedades4.jpg')">
<div class="bolsa flip-container">
<div class="card">
<div class="front">
<img src="novedades4.jpg" alt="Noticia 4">
</div>
<div class="back">
<div class="titulo-flip">Acuaponía creativa</div>
</div>
</div>
</div>
</div>
<div class="noticia" onclick="openPopup('Te invitamos a conocer un poco más de nuestra variedad de talleres diseñados para conectar con la naturaleza, entendiendo el impacto que puede tener en nuestra cotidianidad y en nuestras dinámicas alimentarias en las ciudades.', 'novedades5.jpg')">
<div class="bolsa flip-container">
<div class="card">
<div class="front">
<img src="novedades5.jpg" alt="Sabores florales">
</div>
<div class="back">
<div class="titulo-flip">Sabores florales</div>
</div>
</div>
</div>
</div>
<div class="noticia" onclick="openPopup('Te invitamos a conocer un poco más de nuestra variedad de talleres diseñados para conectar con la naturaleza, entendiendo el impacto que puede tener en nuestra cotidianidad y en nuestras dinámicas alimentarias en las ciudades.', 'novedades6.jpg')">
<div class="bolsa flip-container">
<div class="card">
<div class="front">
<img src="novedades6.jpg" alt="Noticia 6">
</div>
<div class="back">
<div class="titulo-flip">Los sentidos del agua</div>
</div>
</div>
</div>
</div>
</div>
<div class="popup" id="popup">
<div class="popup-content">
<span class="close" onclick="closePopup()">×</span>
<img id="popup-img" src="" alt="Imagen de la noticia" style="width: 100%; border-radius: 8px; margin-bottom: 1rem;">
<p id="popup-text"></p>
</div>
</div>
</body>
</html>
css: body {
margin: 0;
padding: 0;
background-color: #EAF2CE;
font-family: "IBM Plex Mono", monospace;
}
.novedades-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
padding: 2rem;
max-width: 1100px;
margin: 0 auto;
justify-items: center;
}
.noticia {
cursor: pointer;
transition: transform 0.3s ease;
width: 100%;
max-width: 300px;
}
.noticia:hover {
transform: scale(1.03);
}
.bolsa {
background-image: url('bolsa-novedades.png');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
aspect-ratio: 1.5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
position: relative;
padding: 0.8rem;
}
.bolsa img {
width: 60%;
height: 40%;
object-fit: cover;
border-radius: 8px;
}
.bolsa .titulo {
position: absolute;
bottom: 10px;
color: #000;
font-weight: bold;
font-size: 0.9rem;
opacity: 0;
transition: opacity 0.3s ease;
text-align: center;
padding: 0.2rem 0.4rem;
}
.noticia:hover .titulo {
opacity: 1;
}
.popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: none;
justify-content: center;
align-items: center;
z-index: 1000;
}
.popup.show {
display: flex;
}
.popup-content {
background-color: white;
max-width: 90%;
max-height: 90%;
width: 600px;
padding: 1rem;
border-radius: 8px;
overflow-y: auto;
position: relative;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
}
.popup-content .close {
position: absolute;
top: 0.5rem;
right: 1rem;
font-size: 1.5rem;
cursor: pointer;
color: black;
background-color: transparent;
border: none;
}
.popup-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin-bottom: 1rem;
}
@media (max-width: 900px) {
.novedades-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 600px) {
.novedades-grid {
grid-template-columns: 1fr;
padding: 1rem;
}
.popup-content {
max-height: 80%;
width: 90%;
padding: 1rem;
}
}
.bolsa.flip-container {
perspective: 1000px;
padding: 0;
position: relative;
}
.bolsa.flip-container:hover .card {
transform: rotateY(180deg);
cursor: pointer;
}
.card {
width: 100%;
height: 100%;
position: relative;
transition: transform 1s;
transform-style: preserve-3d;
}
.front, .back {
position: absolute;
width: 100%;
height: 100%;
border-radius: 1rem;
backface-visibility: hidden;
display: flex;
justify-content: center;
align-items: center;
padding: 0.8rem;
}
.front img {
width: 60%;
height: 40%;
object-fit: cover;
border-radius: 8px;
}
.back {
transform: rotateY(180deg);
background-color: #EAF2CE;
}
.titulo-flip {
font-weight: bold;
font-size: 1rem;
color: #000;
text-align: center;
padding: 1rem;
}
js: function openPopup(text, imageSrc) {
document.getElementById('popup-text').innerText = text;
document.getElementById('popup-img').src = imageSrc;
document.getElementById('popup').classList.add('show');
}
function closePopup() {
document.getElementById('popup').classList.remove('show');
}
window.openPopup = openPopup;
window.closePopup = closePopup;
import * as THREE from 'three';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
75, window.innerWidth / window.innerHeight, 0.1, 1000
);
camera.position.set(0, 1, 5);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.domElement.style.position = 'absolute';
renderer.domElement.style.top = '0';
renderer.domElement.style.left = '0';
renderer.domElement.style.zIndex = '1';
renderer.domElement.style.pointerEvents = 'none';
document.body.appendChild(renderer.domElement);
// Luces
const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(5, 10, 7.5);
scene.add(directionalLight);
const loader = new GLTFLoader();
let modelRef = null;
const flowerClones = [];
loader.load('/flower.glb', function (gltf) {
const originalModel = gltf.scene;
const s = 0.05;
originalModel.scale.set(s, s, s);
originalModel.rotation.set(Math.PI / 2, 0, 0);
const baseX = -5.0;
const positionsMain = [
{ x: baseX - 0.02, y: 3.80 },
{ x: baseX - 0.90, y: 3.80 },
{ x: baseX - 1.35, y: 3.95 },
{ x: baseX - 0.42, y: 3.80 },
{ x: baseX + 0.45, y: 3.80 },
{ x: baseX + 0.90, y: 3.85 },
{ x: baseX + 1.30, y: 3.95 },
{ x: baseX + 1.60, y: 4.15 },
{ x: baseX - 0.50, y: 4.40 },
{ x: baseX - 0.05, y: 4.40 },
{ x: baseX + 0.45, y: 4.45 },
];
const positionsBackground = [
{ x: baseX - 0.75, y: 4.15 },
{ x: baseX - 0.27, y: 4.15 },
{ x: baseX + 0.20, y: 4.15 },
{ x: baseX + 0.60, y: 4.20 },
{ x: baseX + 0.90, y: 4.20 },
{ x: baseX + 1.20, y: 4.30 },
{ x: baseX - 1.15, y: 4.20 },
];
const allPositions = [...positionsMain, ...positionsBackground];
allPositions.forEach(pos => {
const flower = originalModel.clone();
flower.position.set(pos.x, pos.y, 0);
flower.rotation.set(Math.PI / 2, 0, 0);
flower.scale.set(s, s, s);
scene.add(flower);
flowerClones.push(flower);
});
});
loader.load('/flower2.glb', function (gltf) {
const originalModel = gltf.scene;
const s = 0.005; //tamaño modelo
originalModel.scale.set(s, s, s);
originalModel.rotation.set(Math.PI / 2, 0, 0);
const baseX = 0; // posicion bolsa
const positionsMain = [
{ x: baseX - 1.00, y: 3.80 },
{ x: baseX - 0.65, y: 3.70 },
{ x: baseX - 0.30, y: 3.65 },
{ x: baseX + 0.10, y: 3.90 },
{ x: baseX + 0.05, y: 3.65 },
{ x: baseX + 0.45, y: 3.95 },
{ x: baseX - 0.90, y: 4.15 },
{ x: baseX - 0.10, y: 3.95 },
{ x: baseX + 0.45, y: 3.65 },
{ x: baseX + 0.85, y: 4.00 },
{ x: baseX + 0.85, y: 3.70 },
{ x: baseX + 0.90, y: 4.30 },
{ x: baseX + 1.15, y: 4.10 },
{ x: baseX + 1.25, y: 3.80 },
{ x: baseX + 1.55, y: 3.90 },
{ x: baseX - 0.20, y: 4.30 },
];
const positionsBackground = [
{ x: baseX - 0.50, y: 4.00 },
{ x: baseX + 0.20, y: 4.30 },
{ x: baseX + 0.60, y: 4.15 },
{ x: baseX - 0.50, y: 4.30 },
{ x: baseX - 1.30, y: 3.90 },
];
const allPositions = [...positionsMain, ...positionsBackground];
allPositions.forEach(pos => {
const flower = originalModel.clone();
flower.position.set(pos.x, pos.y, 0);
flower.rotation.set(Math.PI / 2, 0, 0);
flower.scale.set(s, s, s);
scene.add(flower);
});
});
loader.load('/flower3.glb', function (gltf) {
const originalModel = gltf.scene;
const s = 0.3;
originalModel.scale.set(s, s, s);
originalModel.rotation.set(0, 0, 0);
const baseX = 5.2;
const positionsMain = [
{ x: baseX - 0.90, y: 4.10 },
{ x: baseX + 0.45, y: 4.10 },
{ x: baseX - 1.40, y: 4.25 },
{ x: baseX - 0.90, y: 4.40 },
{ x: baseX + 0.15, y: 4.45 },
{ x: baseX + 0.85, y: 4.10 },
{ x: baseX + 1.25, y: 4.10 },
{ x: baseX + 1.55, y: 4.40 },
{ x: baseX + 1.05, y: 4.50 },
];
const positionsBackground = [
{ x: baseX - 0.50, y: 4.10 },
{ x: baseX - 0.35, y: 4.50 },
{ x: baseX + 0.60, y: 4.55 },
{ x: baseX - 0.05, y: 4.10 },
];
const allPositions = [...positionsMain, ...positionsBackground];
allPositions.forEach(pos => {
const flower = originalModel.clone();
flower.position.set(pos.x, pos.y, 0);
flower.rotation.set(0, 0, 0);
flower.scale.set(s, s, s);
scene.add(flower);
});
});
loader.load('/flower4.glb', function (gltf) {
const originalModel = gltf.scene;
const s = 4.0;
originalModel.scale.set(s, s, s);
originalModel.rotation.set(0, 0, 0);
const baseX = -5.0;
const positions = [
{ x: baseX - 0.9, y: 1.4 },
{ x: baseX, y: 1.4 },
{ x: baseX + 0.9, y: 1.4 },
{ x: baseX + 1.2, y: 1.7 },
{ x: baseX - 1.4, y: 1.6 },
{ x: baseX - 0.6, y: 1.8 },
{ x: baseX + 0.5, y: 1.8 },
];
positions.forEach(pos => {
const flower = originalModel.clone();
flower.position.set(pos.x, pos.y, 0);
flower.rotation.set(Math.PI / 2, 0, 0);
flower.scale.set(s, s, s);
scene.add(flower);
});
});
loader.load('/flower5.glb', function (gltf) {
const originalModel = gltf.scene;
const s = 3.0;
originalModel.scale.set(s, s, s);
originalModel.rotation.set(0, 0, 0);
const baseX = 0;
const positions = [
{ x: baseX - 0.6, y: 1.0 },
{ x: baseX, y: 1.0 },
{ x: baseX + 0.6, y: 1.0 },
{ x: baseX + 1.3, y: 1.1 },
{ x: baseX - 1.2, y: 1.1 },
];
positions.forEach(pos => {
const flower = originalModel.clone();
flower.position.set(pos.x, pos.y, 0);
flower.rotation.set(-11, 0, 0);
flower.scale.set(s, s, s);
scene.add(flower);
});
});
loader.load('/flower6.glb', function (gltf) {
const originalModel = gltf.scene;
const s = 5.0;
originalModel.scale.set(s, s, s);
originalModel.rotation.set(0, 0, 0);
const baseX = 5.2;
const positions = [
{ x: baseX - 1.0, y: 1.4 },
{ x: baseX - 1.3, y: 1.1 },
{ x: baseX - 0.7, y: 1.0 },
{ x: baseX - 0.3, y: 1.2 },
{ x: baseX, y: 0.9 },
{ x: baseX, y: 1.5 },
{ x: baseX + 1.0, y: 1.4 },
{ x: baseX + 1.4, y: 1.1 },
{ x: baseX + 0.7, y: 1.0 },
{ x: baseX + 0.3, y: 1.2 },
];
positions.forEach(pos => {
const flower = originalModel.clone();
flower.position.set(pos.x, pos.y, 0);
flower.rotation.set(2, 3, 0);
flower.scale.set(s, s, s);
scene.add(flower);
});
});
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});