This guide provides detailed instructions and examples for creating engaging interactive web content using SVG, CSS, JavaScript, AI tools, and 3D graphics within the WebSim environment.
Scalable Vector Graphics (SVG) are ideal for WebSim projects. They are resolution-independent (always sharp), easily manipulated with code, and often have small file sizes.
<svg> tag in your HTML.width and height attributes (e.g., width="100" height="100").viewBox="min-x min-y width height" attribute (e.g., viewBox="0 0 100 100"). This allows scaling.<svg> tag:
<circle cx="..." cy="..." r="..." /> (center x, center y, radius)<rect x="..." y="..." width="..." height="..." /> (top-left x, top-left y, dimensions)<line x1="..." y1="..." x2="..." y2="..." /> (start x/y, end x/y)<polygon points="..." /> (list of x,y coordinates)<path d="..." /> (most powerful, defines complex shapes with path data)fill="#ff0000", stroke="black", stroke-width="2") or use CSS classes and external/internal stylesheets.fill, stroke, stroke-width, opacity, transform.<!-- 1. SVG Container with dimensions and viewBox -->
<svg width="100" height="100" viewBox="0 0 100 100">
<!-- 2. Circle shape with position, radius, and style attributes -->
<circle id="svg-example-circle" cx="50" cy="50" r="40"
/* @tweakable Border color of the SVG example circle */
stroke="#2c3e50"
/* @tweakable Border width of the SVG example circle */
stroke-width="3"
/* @tweakable Fill color of the SVG example circle */
fill="#e74c3c" />
<!-- 3. Rectangle shape with position, dimensions, and style attributes -->
<rect id="svg-example-rect" x="30" y="30" width="40" height="40"
/* @tweakable Fill color of the SVG example rectangle */
fill="#ecf0f1"
/* @tweakable Opacity of the SVG example rectangle (0-1) */
opacity="0.8"/>
</svg>
CSS provides performant ways to add motion and transitions without complex JavaScript.
@keyframes rule in your CSS. Give it a unique name (e.g., @keyframes pulse { ... }).@keyframes rule, define the styles for different points in the animation timeline using percentages (0%, 50%, 100%) or keywords (from for 0%, to for 100%).transform: scale(1.1);, opacity: 0;).animation shorthand property or individual properties:
animation-name: your-keyframes-name;animation-duration: 2s; (e.g., 2 seconds)animation-timing-function: ease-in-out; (controls acceleration curve)animation-iteration-count: infinite; (how many times to repeat)animation-direction: alternate; (e.g., play forwards then backwards)animation: pulse 2s ease-in-out infinite alternate;
transform (translate, scale, rotate) and opacity for smoother performance, as browsers can often optimize these.background-color: blue;)..my-element:hover { background-color: red; }).transition property to specify which CSS properties should animate, the duration, and the timing function.
transition-property: background-color, transform; (or all)transition-duration: 0.3s;transition-timing-function: ease;transition: background-color 0.3s ease, transform 0.5s ease-out; or transition: all 0.3s ease;
:hover or :focus becoming active).This circle uses a CSS @keyframes animation (pulse) for a background throb and a transition for a smooth color change on hover.
<!-- HTML -->
<svg width="100" height="100" viewBox="0 0 100 100">
<circle class="animated-circle" cx="50" cy="50" r="40" />
</svg>
<!-- CSS -->
<style>
.animated-circle {
/* @tweakable Initial fill color of the CSS animated circle */
fill: #3498db;
/* @tweakable Transition duration for hover color change */
transition: fill 0.3s ease;
cursor: pointer;
/* Apply the keyframe animation */
/* @tweakable Keyframe animation name */
animation-name: pulse;
/* @tweakable Keyframe animation duration */
animation-duration: 2s;
/* @tweakable Keyframe animation timing function */
animation-timing-function: ease-in-out;
/* @tweakable Keyframe animation iteration count */
animation-iteration-count: infinite;
/* @tweakable Keyframe animation direction */
animation-direction: alternate;
/* Ensure transform happens from the center */
transform-origin: center;
}
.animated-circle:hover {
/* @tweakable Hover fill color of the CSS animated circle */
fill: #2980b9;
}
@keyframes pulse {
from {
/* @tweakable Initial scale in pulse animation */
transform: scale(1);
}
to {
/* @tweakable Max scale in pulse animation */
transform: scale(1.1);
}
}
</style>
JavaScript brings your creations to life, allowing dynamic changes and responses to user actions.
id="myButton") or a class (class="clickable") for easy selection.<script type="module"> tag, usually before the closing </body> tag, or ensure it runs after the DOM is loaded.
const myElement = document.getElementById('elementId'); (Best for unique elements)const clickableItems = document.querySelectorAll('.clickableClass'); (Gets multiple elements)const firstClickable = document.querySelector('.clickableClass'); (Gets the first matching element).addEventListener() method on the selected element(s).
'click', 'mouseover', 'pointerdown', 'input').myElement.addEventListener('click', () => { console.log('Element clicked!'); });
element.style.property = 'value';)element.classList.add('active');, element.classList.remove('highlight');, element.classList.toggle('clicked');)element.setAttribute('fill', 'red');)gsap.to(element, { duration: 0.5, x: 100 });)querySelectorAll, loop through the resulting NodeList to attach the listener to each one.
Example: clickableItems.forEach(item => { item.addEventListener('click', handleItemClick); });
<!-- HTML -->
<svg width="100" height="100" viewBox="0 0 100 100">
<rect id="interactive-square" class="interactive-square" x="25" y="25" width="50" height="50" />
</svg>
<!-- CSS -->
<style>
.interactive-square {
/* @tweakable Initial fill color of the interactive square */
fill: #e74c3c;
cursor: pointer;
/* @tweakable Transition settings for interactive square */
transition: transform 0.2s ease, fill 0.2s ease;
/* Ensure transform-origin is center for rotation/scale */
transform-origin: center;
}
.interactive-square.clicked {
/* @tweakable Rotation and scale when interactive square is clicked */
transform: rotate(45deg) scale(0.8);
/* @tweakable Fill color when interactive square is clicked */
fill: #c0392b;
}
</style>
Coordinating the timing of different visual elements, animations, and potentially audio cues is crucial for creating polished and professional-looking interactive experiences. WebSim provides several ways to manage timing.
transition-duration, animation-delay, animation-duration). Synchronization between multiple CSS-animated elements can become complex.setTimeout) or at regular intervals (setInterval). Useful for simple timed events, but not ideal for smooth visual animations as they aren't synchronized with the browser's rendering cycle and can lead to jitter or drift over time.
// Example: Trigger an alert after 2 seconds
/* @tweakable Delay in milliseconds for setTimeout example */
const simpleDelay = 2000;
setTimeout(() => {
alert('Two seconds passed!');
}, simpleDelay);
// Example: Basic rAF loop structure
let startTime = null;
function myAnimationLoop(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime; // Time since animation started
// --- Update element styles based on 'elapsed' time ---
// e.g., element.style.transform = `translateX(${elapsed * 0.1}px)`;
// Continue the loop
requestAnimationFrame(myAnimationLoop);
}
// Start the loop
requestAnimationFrame(myAnimationLoop);
// Example: GSAP timeline concept (requires GSAP import)
import { gsap } from "gsap";
/* @tweakable Duration of the first animation (box move) */
const boxMoveDuration = 1.0;
/* @tweakable Duration of the second animation (circle fade) */
const circleFadeDuration = 0.5;
/* @tweakable Delay before the circle fade starts relative to box move start */
const circleFadeDelay = 0.5; // Starts 0.5s after the timeline begins
if (gsap) {
const tl = gsap.timeline({ /* @tweakable Repeat count for the timeline (-1 for infinite) */ repeat: 0 });
tl.to("#some-box", { duration: boxMoveDuration, x: 100, ease: "power1.inOut" }); // Move box
tl.to("#some-circle", { duration: circleFadeDuration, opacity: 0, ease: "none" }, circleFadeDelay); // Fade circle, starting at 0.5s
}
transitionend or animationend in CSS to trigger JavaScript functions when a CSS animation finishes. Be aware of potential issues if animations are interrupted or run multiple times.audioElement.play()) at specific points in your animation sequence (e.g., in a GSAP timeline callback, an animationend event, or at a certain time in a requestAnimationFrame loop). Since you cannot upload audio files directly, you would typically use sounds available via browser APIs (like the Web Audio API for generating simple tones) or reference external sound files if permitted by the platform context.This example moves a rectangle across and fades out a circle using requestAnimationFrame.
// HTML
<svg id="raf-scene" width="300" height="100" viewBox="0 0 300 100">
<rect id="raf-rect" x="10" y="40" width="20" height="20"></rect>
<circle id="raf-circle" cx="150" cy="50" r="15"></circle>
</svg>
// JavaScript
/* @tweakable Total animation duration in milliseconds */
const animationDuration = 3000;
/* @tweakable Final x position of the rectangle */
const finalRectX = 270;
/* @tweakable Delay before circle starts fading (in milliseconds) */
const circleFadeDelay = 1000;
let animationId = null;
let startTime = null;
function animateElements(timestamp) {
if (!startTime) startTime = timestamp;
const elapsed = timestamp - startTime;
const progress = Math.min(1, elapsed / animationDuration);
// Move rectangle from left to right
const rectX = 10 + (finalRectX - 10) * progress;
rafRect.setAttribute('x', rectX);
// Fade out circle after delay
if (elapsed > circleFadeDelay) {
const circleProgress = Math.min(1, (elapsed - circleFadeDelay) /
(animationDuration - circleFadeDelay));
rafCircle.style.opacity = 1 - circleProgress;
}
if (progress < 1) {
animationId = requestAnimationFrame(animateElements);
}
}
startBtn.addEventListener('click', () => {
if (animationId) cancelAnimationFrame(animationId);
startTime = null;
animationId = requestAnimationFrame(animateElements);
});
WebSim provides built-in access to powerful AI tools, including a Large Language Model (LLM) for text generation and analysis, and an Image/Video Generation model.
These tools are accessed via the globally available websim object (e.g., websim.chat.completions.create and websim.imageGen). Ensure the "Enable LLM" or equivalent setting is active for your project.
Interact with the LLM to generate text, answer questions, analyze content, or even get structured JSON data.
json: true in the request and providing a schema description in the prompt.image_url content type. This requires uploading the image first (see File Upload below).// Basic AI Chat Example
let conversationHistory = [];
async function sendChatMessage() {
const userMessage = document.getElementById("chat-prompt-1").value.trim();
if (!userMessage) return;
const responseElement = document.getElementById("chat-response-1");
responseElement.textContent = "Thinking...";
// Add user message to history
const newMessage = { role: "user", content: userMessage };
conversationHistory.push(newMessage);
try {
/* @tweakable System message for the AI assistant */
const systemMessage = "You are a helpful assistant who provides concise answers.";
/* @tweakable Maximum conversation history length */
const maxHistoryLength = 10;
// Keep only the most recent messages
conversationHistory = conversationHistory.slice(-maxHistoryLength);
const completion = await websim.chat.completions.create({
messages: [
{ role: "system", content: systemMessage },
...conversationHistory
]
});
// Display the response
responseElement.textContent = completion.content;
// Add the AI's response to history
conversationHistory.push(completion);
} catch (error) {
responseElement.textContent = "Error: " + error.message;
}
}
// AI Chat with JSON Output Example
async function getJsonFromAI() {
const prompt = document.getElementById("chat-prompt-2").value.trim();
if (!prompt) return;
const responseElement = document.getElementById("chat-response-2");
responseElement.textContent = "Thinking...";
try {
const completion = await websim.chat.completions.create({
messages: [
{
role: "system",
content: `Provide information in JSON format following this schema:
{
"summary": string, // A brief summary
"details": string, // More detailed explanation
"confidence": number // 0-1 rating of your confidence
}`
},
{ role: "user", content: prompt }
],
json: true // Request JSON output
});
// Parse and display the response
const result = JSON.parse(completion.content);
responseElement.innerHTML = `Summary: ${result.summary}
Details: ${result.details}
Confidence: ${result.confidence}`;
} catch (error) {
responseElement.textContent = "Error: " + error.message;
}
}
Generate images from text prompts. You can specify aspect ratios, dimensions, or use seeds for variations.
aspect_ratio, width/height, and seed.url property pointing to the generated image.Image will appear here...
// AI Image Generation Example
async function generateImage() {
const prompt = document.getElementById("image-prompt").value.trim();
if (!prompt) return;
const resultElement = document.getElementById("image-result");
resultElement.innerHTML = "Generating image...";
try {
/* @tweakable Aspect ratio for generated images */
const aspectRatio = "16:9";
const result = await websim.imageGen({
prompt: prompt,
aspect_ratio: aspectRatio
});
if (result && result.url) {
// Create and display the image
const img = document.createElement("img");
img.src = result.url;
img.alt = prompt;
img.className = "ai-image-preview";
resultElement.innerHTML = "";
resultElement.appendChild(img);
} else {
throw new Error("Invalid response from image generator");
}
} catch (error) {
resultElement.textContent = "Error: " + error.message;
}
}
To use images as input for the LLM (e.g., asking questions about an image), you first need to upload the image file and get its URL using websim.upload(file). This function returns a promise that resolves with the URL of the uploaded file.
// File Upload Example
document.getElementById("file-input").addEventListener("change", (event) => {
const fileInfo = document.getElementById("file-name");
if (event.target.files.length > 0) {
fileInfo.textContent = event.target.files[0].name;
} else {
fileInfo.textContent = "No file selected";
}
});
async function uploadFile() {
const fileInput = document.getElementById("file-input");
const resultElement = document.getElementById("upload-result");
if (!fileInput.files || fileInput.files.length === 0) {
resultElement.textContent = "Please select a file first";
return;
}
const file = fileInput.files[0];
resultElement.textContent = "Uploading...";
try {
// Upload to S3
const url = await websim.upload(file);
resultElement.textContent = "File uploaded successfully. URL: " + url;
// Optionally display the image
if (file.type.startsWith("image/")) {
const img = document.createElement("img");
img.src = url;
img.alt = "Uploaded image";
img.className = "ai-image-preview";
resultElement.appendChild(img);
}
} catch (error) {
resultElement.textContent = "Error uploading file: " + error.message;
}
}
WebSim aims to provide tools for various creative tasks. While not yet implemented, this section describes how a potential AI video generation feature might work.
Imagine a function like websim.videoGen that takes a text prompt and parameters like duration or style to generate short video clips.
duration_seconds), aspect ratio (aspect_ratio), or motion intensity (motion_level).url property pointing to the generated video file (e.g., an MP4).Video preview or status will appear here...
(Note: Video Generation API (websim.videoGen) is hypothetical and not yet implemented.)
// Future AI Video Generation Example (hypothetical)
async function generateVideo() {
const prompt = document.getElementById("video-prompt").value.trim();
if (!prompt) return;
const resultElement = document.getElementById("video-result");
resultElement.innerHTML = "Generating video...";
try {
/* @tweakable Duration of generated video in seconds */
const duration = 3;
/* @tweakable Motion intensity (low, medium, high) */
const motionLevel = "medium";
// This is hypothetical - not yet implemented
const result = await websim.videoGen({
prompt: prompt,
duration_seconds: duration,
motion_level: motionLevel
});
if (result && result.url) {
// Create and display the video
const video = document.createElement("video");
video.src = result.url;
video.controls = true;
video.autoplay = false;
video.loop = true;
video.muted = false;
video.className = "ai-video-preview";
resultElement.innerHTML = "";
resultElement.appendChild(video);
}
} catch (error) {
resultElement.textContent = "Error: " + error.message;
}
}
Integrate high-quality 3D graphics into your WebSim projects using libraries like Three.js.
Modern web browsers support WebGL (Web Graphics Library), a JavaScript API for rendering interactive 2D and 3D graphics without plug-ins. However, using WebGL directly can be complex. Libraries like Three.js provide higher-level abstractions, making it much easier to create and manage 3D scenes.
Three.js is the de facto standard for web-based 3D graphics.
<head> of this page).new THREE.Scene()).PerspectiveCamera, OrthographicCamera).<canvas> element (usually WebGLRenderer).BoxGeometry, SphereGeometry, TorusKnotGeometry).MeshStandardMaterial, MeshBasicMaterial).new THREE.Mesh(geometry, material)).AmbientLight, DirectionalLight, PointLight).requestAnimationFrame) that redraws the scene on each frame, allowing for animation and interaction.GLTFLoader to import models created in 3D software (Blender, Maya, etc.). The glTF/GLB format is recommended for web use.OrbitControls to allow users to navigate the scene.This demonstrates embedding a basic Three.js scene directly within this page. You can drag to orbit the Torus Knot.
// Basic Three.js setup for a Torus Knot
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Scene setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x333333);
// Camera setup
/* @tweakable Camera field of view (degrees) */
const cameraFov = 75;
const camera = new THREE.PerspectiveCamera(
cameraFov,
canvas.clientWidth / canvas.clientHeight,
0.1,
1000
);
camera.position.z = 5;
// Renderer setup
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
// Torus Knot geometry
/* @tweakable Radius of the Torus Knot */
const knotRadius = 0.7;
/* @tweakable Radius of the Torus Knot's tube */
const knotTubeRadius = 0.2;
/* @tweakable Segments in the Torus Knot's main radius */
const knotTubularSegments = 100;
/* @tweakable Segments around the Torus Knot's tube */
const knotRadialSegments = 16;
const geometry = new THREE.TorusKnotGeometry(
knotRadius,
knotTubeRadius,
knotTubularSegments,
knotRadialSegments
);
// Material
/* @tweakable Color of the mesh material */
const materialColor = 0x1E90FF;
const material = new THREE.MeshStandardMaterial({
color: materialColor,
metalness: 0.5,
roughness: 0.4
});
// Create mesh and add to scene
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Add lights
const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(2, 3, 4);
scene.add(directionalLight);
// Add OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// Animation loop
function animate() {
requestAnimationFrame(animate);
mesh.rotation.x += 0.005;
mesh.rotation.y += 0.008;
controls.update();
renderer.render(scene, camera);
}
animate();
WebSim provides several templates to help you get started quickly with common project types. These templates come with the necessary structure and code organization to implement specific features.
A complete 3D environment using Three.js, with orbit controls, lighting, and responsive canvas setup.
Use Template// See template-simple-3d.html for the full code
// This template includes:
// - Scene, camera, renderer setup
// - Responsive canvas resizing
// - Orbit controls
// - Basic mesh with material
// - Lighting setup
// - Animation loop
// - Extensive tweakable parameters
A ready-to-use SVG scene with click and drag interactions, CSS animations, and GSAP integration.
Use Template// See template-interactive-svg.html for the full code
// This template includes:
// - Responsive SVG layout
// - Click interactions
// - CSS animations
// - GSAP-powered drag functionality
// - Tweakable design parameters
A template for creating an AI-powered image gallery using WebSim's image generation capabilities.
Use Template// See template-ai-gallery.html for the full code
// This template includes:
// - Gallery grid layout
// - Integration with websim.imageGen
// - Loading states
// - Error handling
// - Responsive design
// - Tweakable design parameters
WebSim provides built-in capabilities for creating multiplayer experiences and persisting data between sessions. This allows you to build collaborative projects, real-time games, and applications that remember user data.
The WebSim Socket provides easy-to-use APIs for real-time communication between users:
// Initialize the WebSim Socket
const room = new WebsimSocket();
// Wait for initialization
await room.initialize();
// Subscribe to presence updates (other users' states)
room.subscribePresence((currentPresence) => {
// Update UI when any user's presence changes
updatePlayerList(room.presence);
});
// Update your own presence (position, state, etc.)
/* @tweakable Player starting X position */
const startX = 100;
/* @tweakable Player starting Y position */
const startY = 150;
room.updatePresence({
x: startX,
y: startY,
health: 100,
username: "Player1" // Will be automatically provided
});
// Player Presence Display Example
function updatePlayerList(presence) {
const playerList = document.getElementById("player-list");
playerList.innerHTML = "";
// Convert presence object into an array of players
const players = Object.entries(presence).map(([clientId, data]) => {
return {
id: clientId,
username: room.peers[clientId]?.username || "Unknown",
avatarUrl: room.peers[clientId]?.avatarUrl,
x: data.x || 0,
y: data.y || 0
};
});
// Create a player element for each connected user
players.forEach(player => {
const playerElement = document.createElement("div");
playerElement.className = "player";
const avatar = document.createElement("div");
avatar.className = "player-avatar";
avatar.textContent = player.username.charAt(0).toUpperCase();
const name = document.createElement("div");
name.className = "player-name";
name.textContent = player.username;
const position = document.createElement("div");
position.className = "player-position";
position.textContent = `X: ${Math.round(player.x)}, Y: ${Math.round(player.y)}`;
playerElement.appendChild(avatar);
playerElement.appendChild(name);
playerElement.appendChild(position);
playerList.appendChild(playerElement);
});
}
WebSim also provides a database-like system for persisting records between sessions:
// Creating a new record
const post = await room.collection('post').create({
message: "Hello world!",
// id, username, and created_at are automatically added
});
// Getting all records of a specific type
const posts = room.collection('post').getList();
// Filtering records
const replies = room.collection('post')
.filter({ parent_id: post.id })
.getList();
// Updating a record (only works for records you created)
await room.collection('post').update(post.id, {
message: "Updated message"
});
// Subscribing to changes
room.collection('post').subscribe(function(posts) {
// This runs whenever the posts collection changes
displayPosts(posts);
});
// Simple Chat System Example
const room = new WebsimSocket();
// Initialize the chat system
async function initChat() {
await room.initialize();
// Subscribe to messages
room.collection('message').subscribe(function(messages) {
displayMessages(messages);
});
// Send message handler
document.getElementById('send-button').addEventListener('click', async () => {
const input = document.getElementById('message-input');
const text = input.value.trim();
if (text) {
await room.collection('message').create({
text: text,
/* @tweakable Message type (optional tag) */
type: 'chat'
});
input.value = '';
}
});
// Allow Enter key to send
document.getElementById('message-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
document.getElementById('send-button').click();
}
});
}
// Display messages in the UI
function displayMessages(messages) {
const container = document.getElementById('messages');
container.innerHTML = '';
// Sort messages by time (oldest first)
const sortedMessages = [...messages].sort((a, b) =>
new Date(a.created_at) - new Date(b.created_at)
);
sortedMessages.forEach(msg => {
const messageEl = document.createElement('div');
messageEl.style = 'padding: 5px; margin-bottom: 5px; background-color: #f1f1f1; border-radius: 4px;';
/* @tweakable Different color for user's own messages */
if (msg.username === room.peers[room.clientId]?.username) {
messageEl.style.backgroundColor = '#e1f5fe';
}
messageEl.innerHTML = `${msg.username}: ${msg.text}`;
container.appendChild(messageEl);
});
// Scroll to bottom
container.scrollTop = container.scrollHeight;
}
For game-specific shared state, use room.roomState to sync data across all clients:
// Update shared room state (e.g., world objects)
room.updateRoomState({
objectPositions: {
"rock-1": { x: 100, y: 200 },
"tree-1": { x: 150, y: 300 }
}
});
// Subscribe to room state changes
room.subscribeRoomState((currentRoomState) => {
// Update world objects in the UI
updateWorldObjects(currentRoomState.objectPositions);
});
Creating smooth, responsive web experiences requires attention to performance. This section covers essential techniques to optimize your WebSim projects.
offsetWidth) and then making style changes, as this forces the browser to recalculate layout multiple times. Batch your reads and writes:
// Poor performance (causes multiple layouts)
const box = document.getElementById('box');
box.style.width = (box.offsetWidth + 10) + 'px'; // Read then write
box.style.height = (box.offsetHeight + 10) + 'px'; // Read then write
// Better performance (batched reads and writes)
const box = document.getElementById('box');
/* @tweakable Width adjustment amount */
const widthAdjustment = 10;
/* @tweakable Height adjustment amount */
const heightAdjustment = 10;
// Batch reads
const width = box.offsetWidth;
const height = box.offsetHeight;
// Batch writes
box.style.width = (width + widthAdjustment) + 'px';
box.style.height = (height + heightAdjustment) + 'px';
transform and opacity don't trigger layout and can be hardware-accelerated.
/* Causes layout and paint on every frame (poor) */
.element {
/* @tweakable Position animation duration */
transition: left 0.3s ease, top 0.3s ease;
}
/* Only triggers composite operations (better) */
.element {
/* @tweakable Transform animation duration */
transition: transform 0.3s ease;
}
will-change property to hint to the browser which elements will animate, but use it sparingly:
.moving-element {
/* @tweakable Properties that will change during animation */
will-change: transform, opacity;
}
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
/* @tweakable Debounce wait time in milliseconds */
timeout = setTimeout(later, wait);
};
}
// Example usage:
const handleResize = debounce(() => {
// Your resize logic here
updateLayoutBasedOnSize();
}, 150); // Execute at most once every 150ms
window.addEventListener('resize', handleResize);
requestAnimationFrame for animations and visual updates that need to sync with the browser's render cycle.// Poor performance (multiple DOM operations)
const list = document.getElementById('myList');
/* @tweakable Number of items to add */
const itemCount = 100;
for (let i = 0; i < itemCount; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
list.appendChild(item); // Causes reflow each time
}
// Better performance (batched DOM operations)
const list = document.getElementById('myList');
const fragment = document.createDocumentFragment();
for (let i = 0; i < itemCount; i++) {
const item = document.createElement('li');
item.textContent = `Item ${i}`;
fragment.appendChild(item); // No reflow
}
list.appendChild(fragment); // Single reflow
<img src="https://sekai-public-access.s3.us-east-1.amazonaws.com/websim-migration/Doktor_Are/websim-creation-manual-high-quality-visualizations/image.jpg" loading="lazy" alt="Description" />
<use> to reuse elements:
<svg>
<defs>
<circle id="reusable-circle" cx="10" cy="10" r="5" fill="blue" />
</defs>
<use href="#reusable-circle" x="0" y="0" />
<use href="#reusable-circle" x="25" y="25" />
<use href="#reusable-circle" x="50" y="50" />
</svg>
Regularly test your WebSim projects for performance using browser developer tools:
This section provides guidelines and best practices for creating high-quality, maintainable WebSim projects.
@tweakable annotations for parameters that might need adjustment.async function fetchData() {
const dataContainer = document.getElementById('data-container');
// Show loading state
dataContainer.innerHTML = '<div class="loading-spinner"></div>';
try {
const response = await fetch('/api/data');
const data = await response.json();
// Display data
dataContainer.innerHTML = renderDataToHTML(data);
} catch (error) {
// Show error state
dataContainer.innerHTML = `<div class="error-message">${error.message}</div>`;
}
}
/* Base styles for mobile first */
.container {
padding: 15px;
/* @tweakable Container width on mobile */
width: 100%;
}
/* Adjust for larger screens */
@media (min-width: 768px) {
.container {
/* @tweakable Container width on tablets */
width: 750px;
margin: 0 auto;
}
}
@media (min-width: 1024px) {
.container {
/* @tweakable Container width on desktops */
width: 980px;
}
}
<button>, <nav>, etc.)// Vague prompt (less effective)
const result = await websim.imageGen({
prompt: "A landscape"
});
// Specific prompt (more effective)
const result = await websim.imageGen({
prompt: "A serene mountain landscape at sunset with a lake reflecting orange and purple skies, in a watercolor style"
});
// Critical data (update immediately)
room.updatePresence({
x: player.position.x,
y: player.position.y,
state: player.currentState
});
// Less critical data (update periodically)
/* @tweakable Interval for updating non-critical data (ms) */
const statsUpdateInterval = 5000;
setInterval(() => {
room.updatePresence({
score: player.score,
itemsCollected: player.inventory.length
});
}, statsUpdateInterval);
// Lower detail for mobile or lower-powered devices
/* @tweakable Polygon count adjustment based on device capability */
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const polygonMultiplier = isMobile ? 0.5 : 1;
const sphereGeometry = new THREE.SphereGeometry(
radius,
/* @tweakable Base width segments for sphere geometry */
Math.floor(32 * polygonMultiplier),
/* @tweakable Base height segments for sphere geometry */
Math.floor(24 * polygonMultiplier)
);
This table summarizes when to use different techniques based on your project requirements:
| Technique | Best For | Limitations |
|---|---|---|
| SVG | Vector graphics, interactive diagrams, data visualizations | Can become slow with many elements (100s+) |
| CSS Animations | Simple UI animations, transitions, hover effects | Limited control flow, difficult to coordinate multiple animations |
| Canvas 2D | Pixel-based graphics, complex animations with many objects | No built-in object model, requires manual redrawing |
| WebGL/Three.js | 3D visualizations, games, complex simulations | Steeper learning curve, higher performance requirements |
| AI Generation | Dynamic content creation, natural language interfaces | Less predictable outputs, API call overhead |