Meta Description" name="description" />

Share this result

Previews are deleted daily. Get a permanent share link sent to your inbox:
Script
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Textbook-Style Electric Field lines</title> <style> body { margin: 0; overflow: hidden; background-color: #f0f0f0; font-family: 'Courier New', Courier, monospace; color: #333; } /* Educational Sidebar (styled like the textbook) */ #textbook-panel { position: absolute; top: 0; right: 0; width: 320px; height: 100%; background: #fafafa; border-left: 2px solid #999; padding: 20px; box-sizing: border-box; overflow-y: auto; box-shadow: -5px 0 15px rgba(0,0,0,0.1); } h2 { border-bottom: 2px solid #333; padding-bottom: 10px; margin-top: 0; } h3 { margin-top: 25px; color: #444; text-transform: uppercase; font-size: 1em; } p { line-height: 1.5; font-size: 0.95em; } .figure-caption { font-style: italic; color: #555; margin-top: 5px; margin-bottom: 15px; display: block; font-size: 0.9em;} /* Controls Area */ #controls { position: absolute; bottom: 20px; left: 20px; width: 280px; background: rgba(255, 255, 255, 0.9); padding: 15px; border-radius: 8px; border: 1px solid #ccc; box-shadow: 0 4px 10px rgba(0,0,0,0.1); } .control-group { margin-bottom: 15px; } label { display: block; font-size: 0.9em; margin-bottom: 8px; font-weight: bold;} button { width: 100%; padding: 10px; margin-bottom: 8px; cursor: pointer; background: #fff; border: 2px solid #333; color: #333; font-weight: bold; text-transform: uppercase; transition: 0.2s; font-family: inherit; } button:hover { background: #333; color: #fff; } button.active { background: #333; color: #fff; border-color: #333;} .reset-btn { background: #fee; border-color: #a33; color: #a33; margin-top: 10px;} .reset-btn:hover { background: #a33; color: #fff; } input[type="range"] { width: 100%; cursor: pointer; accent-color: #333; } </style> </head> <body> <div id="canvas-container"></div> <div id="textbook-panel"> <h2>PHYSICS: Electrostatics</h2> <h3>Electric Field Patterns</h3> <p>In order to draw the electric field lines, we place positive test charges at different places. The electric field created by the source charges will exert a force on these test charges. The lines of force (or electric field lines) are drawn tangent to the net force vector at every point.</p> <p>Use the presets below to visualize the classic patterns from your textbook image:</p> <div class="control-group"> <button id="btn-positive" onclick="loadPreset('positive')">(1) Single Positive Charge</button> <span class="figure-caption">Field lines are directed radially outward.</span> <button id="btn-negative" onclick="loadPreset('negative')">(2) Single Negative Charge</button> <span class="figure-caption">Field lines are directed radially inward.</span> <button id="btn-like" onclick="loadPreset('like')">(3) Two Like Charges (+, +)</button> <span class="figure-caption">Field lines repel. Middle region shows a zero field spot (neutral zone).</span> <button id="btn-unlike" onclick="loadPreset('unlike')">(4) Two Unlike Charges (+, -)</button> <span class="figure-caption">Field lines start from positive and end on negative. Attractive force.</span> </div> <div class="control-group" style="border-top: 1px solid #ccc; pt: 15px;"> <label>Line Density (Drafting Precision)</label> <input type="range" id="density" min="8" max="48" step="4" value="24" oninput="updateDensity(this.value)"> </div> <button class="reset-btn" onclick="clearScene()">Clear Canvas</button> </div> <script type="importmap"> { "imports": { "three": "https://unpkg.com/three@0.160.0/build/three.module.js", "three/addons/": "https://unpkg.com/three@0.160.0/examples/jsm/" } } </script> <script type="module"> import * as THREE from 'three'; import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // --- Core Configuration --- let scene, camera, renderer, controls; let charges = []; // {mesh, strength, originalY} let fieldLineGroup; let currentPreset = 'unlike'; let lineDensity = 24; // Lines per charge // Physics constants adjusted for visual scaling const k = 100; const stepSize = 0.1; // Length of each line segment const maxSteps = 1000; // Prevent infinite lines const stopDistanceSq = 0.6 * 0.6; // Stop line near a charge init(); animate(); function init() { // 1. Setup Scene (White background, orthographic-like view) scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); // Textbook paper color // Orthographic camera makes 2D diagrams easier const aspect = (window.innerWidth - 320) / window.innerHeight; // Account for panel const d = 20; camera = new THREE.OrthographicCamera(-d * aspect, d * aspect, d, -d, 1, 1000); camera.position.set(0, 0, 50); // Look straight down the Z axis camera.lookAt(0, 0, 0); renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth - 320, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('canvas-container').appendChild(renderer.domElement); // Minimalist grid (optional, but helps spatial awareness) // scene.add(new THREE.GridHelper(100, 100, 0xdddddd, 0xeeeeee)); // 2. Objects Group fieldLineGroup = new THREE.Group(); scene.add(fieldLineGroup); // 3. Setup interaction (Pan and Zoom only) controls = new OrbitControls(camera, renderer.domElement); controls.enableRotate = false; // Keep it 2D controls.screenSpacePanning = true; // 4. Load initial state loadPreset('unlike'); window.addEventListener('resize', onWindowResize); } // --- Core Physics Logic (Field Line Tracing) --- // Calculate Net E-Field Vector at a point function calculateField(x, y) { let netField = new THREE.Vector2(0, 0); for (const charge of charges) { let pos = charge.mesh.position; let dx = x - pos.x; let dy = y - pos.y; let distSq = dx * dx + dy * dy; if (distSq < 0.1) continue; // Avoid singularity let mag = (k * charge.strength) / distSq; let dir = new THREE.Vector2(dx, dy).normalize(); netField.add(dir.multiplyScalar(mag)); } return netField; } // Trace a single field line from a starting point function traceLine(startX, startY, strength) { let points = []; let currentPos = new THREE.Vector2(startX, startY); points.push(new THREE.Vector3(currentPos.x, currentPos.y, 0)); let lineDirection = strength > 0 ? 1 : -1; // + charges trace outward, - charges inward for (let i = 0; i < maxSteps; i++) { let eField = calculateField(currentPos.x, currentPos.y); if (eField.length() < 0.01) break; // Field too weak (neutral zone) // Move stepAlong field let step = eField.normalize().multiplyScalar(stepSize * lineDirection); currentPos.add(step); points.push(new THREE.Vector3(currentPos.x, currentPos.y, 0)); // Check if we hit another charge or went too far let hitCharge = false; for (const c of charges) { if (currentPos.distanceToSquared(c.mesh.position) < stopDistanceSq) { // Snap last point to charge surface visually points[points.length-1].copy(c.mesh.position); hitCharge = true; break; } } if (hitCharge || currentPos.length() > 100) break; } if (points.length < 2) return null; // Geometry needs 2 points // Create the line mesh const geometry = new THREE.BufferGeometry().setFromPoints(points); const material = new THREE.LineBasicMaterial({ color: 0x333333, linewidth: 1 }); const line = new THREE.Line(geometry, material); // Add Arrowhead like the diagrams if (points.length > 10) { // Only add arrows to decent sized lines // Place arrow roughly 3/4 along the line const arrowIndex = Math.floor(points.length * 0.7); const p1 = points[arrowIndex]; const p2 = points[arrowIndex + 1]; if(p1 && p2) { const dir = new THREE.Vector3().subVectors(p2, p1).normalize(); // Color arrow same as charge (textbook style varies, let's keep it monochromatic for clarity) const arrowHelper = new THREE.ArrowHelper(dir, p1, 0.8, 0x333333, 0.3, 0.3); line.add(arrowHelper); } } return line; } // --- Visualization Management --- function rebuildScene() { // Clear old lines while (fieldLineGroup.children.length > 0) { fieldLineGroup.remove(fieldLineGroup.children[0]); } // 1. Trace lines starting angularly around each charge charges.forEach(charge => { const numLines = lineDensity; const radius = 0.6; // Start just outside the sphere surface for (let i = 0; i < numLines; i++) { const angle = (i / numLines) * Math.PI * 2; const startX = charge.mesh.position.x + Math.cos(angle) * radius; const startY = charge.mesh.position.y + Math.sin(angle) * radius; const lineMesh = traceLine(startX, startY, charge.strength); if (lineMesh) fieldLineGroup.add(lineMesh); } }); } // --- Interaction & Presets --- function createChargeMesh(strength) { const geo = new THREE.SphereGeometry(0.5, 16, 16); const mat = new THREE.MeshBasicMaterial({ color: strength > 0 ? 0xff4444 : 0x4444ff, // Red for +, Blue for - }); const mesh = new THREE.Mesh(geo, mat); // Add text label (+ or -) like in diagrams const canvas = document.createElement('canvas'); canvas.width = 64; canvas.height = 64; const ctx = canvas.getContext('2d'); ctx.font = 'Bold 40px Courier New'; ctx.fillStyle = 'white'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(strength > 0 ? '+' : '-', 32, 35); const texture = new THREE.CanvasTexture(canvas); const spriteMat = new THREE.SpriteMaterial({ map: texture }); const sprite = new THREE.Sprite(spriteMat); sprite.scale.set(1.5, 1.5, 1); mesh.add(sprite); // Attach label to sphere return mesh; } function addCharge(strength, x, y) { const mesh = createChargeMesh(strength); mesh.position.set(x, y, 0); scene.add(mesh); charges.push({ mesh, strength }); } // Global Access Functions for HTML Buttons window.clearScene = function() { charges.forEach(c => scene.remove(c.mesh)); charges = []; // Remove active class from buttons document.querySelectorAll('.control-group button').forEach(b=>b.classList.remove('active')); rebuildScene(); } window.updateDensity = function(val) { lineDensity = parseInt(val); rebuildScene(); } window.loadPreset = function(name) { window.clearScene(); currentPreset = name; // UI feedback document.getElementById(`btn-${name}`).classList.add('active'); switch(name) { case 'positive': addCharge(1, 0, 0); break; case 'negative': addCharge(-1, 0, 0); break; case 'like': addCharge(1, -6, 0); addCharge(1, 6, 0); break; case 'unlike': default: addCharge(1, -6, 0); addCharge(-1, 6, 0); break; } rebuildScene(); } // --- Main Loop --- function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } function onWindowResize() { const width = window.innerWidth - 320; const aspect = width / window.innerHeight; const d = camera.top; // Keep vertical scale consistent camera.left = -d * aspect; camera.right = d * aspect; camera.updateProjectionMatrix(); renderer.setSize(width, window.innerHeight); } </script> </body> </html>
Landing Page
This ad does not have a landing page available
Network Timeline
Performance Summary

3

Requests

2

Domains

272KB

Transfer Size

1286KB

Content Size

310.0ms

Dom Content Loaded

200.0ms

First Paint

311.0ms

Load Time
Domain Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...
Timings (ms)
Loading...
Total Time
Loading...
Content Breakdown
Transfer Size (bytes)
Loading...
Content Size (bytes)
Loading...
Header Size (bytes)
Loading...
Requests
Loading...