TensorFlow.js: Machine Learning Directly in the Browser With JavaScript
Hello HaWkers, what if you could run machine learning models directly in the user's browser, without needing a server? In 2025, this is no longer the future - it's reality, and TensorFlow.js is the tool that makes it possible.
In this guide, we'll explore how to use TensorFlow.js to create intelligent applications that run 100% client-side.
Why Machine Learning in the Browser
Running ML in the browser offers unique advantages that many developers still don't explore.
Benefits
Privacy:
- Data never leaves the user's device
- No LGPD/GDPR concerns for processing
- Ideal for sensitive applications
Performance:
- No network latency for inference
- Uses device GPU via WebGL
- Real-time responses
Cost:
- Zero server cost for inference
- Unlimited scale without increasing infrastructure
- Democratizes ML access
Experience:
- Works offline
- Real-time interactivity
- Doesn't depend on connection
🧠 Context: TensorFlow.js is used by millions of developers and companies like Google, Spotify, and Airbnb for browser ML.
Getting Started With TensorFlow.js
Let's set up a project and understand the basic concepts.
Installation
# Via NPM
npm install @tensorflow/tfjs
# Or via CDN in HTML
# <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs"></script>Fundamental Concepts
import * as tf from '@tensorflow/tfjs';
// Tensors: Multidimensional arrays
const tensor1D = tf.tensor([1, 2, 3, 4]);
const tensor2D = tf.tensor([[1, 2], [3, 4]]);
// Shapes: Tensor dimensions
console.log(tensor2D.shape); // [2, 2]
// Basic operations
const a = tf.tensor([1, 2, 3]);
const b = tf.tensor([4, 5, 6]);
const sum = a.add(b); // [5, 7, 9]
const product = a.mul(b); // [4, 10, 18]
const mean = a.mean(); // 2
// IMPORTANT: Clean up memory
tensor1D.dispose();
tensor2D.dispose();
// Or use tf.tidy() for automatic cleanupMemory Management
// tf.tidy() automatically cleans intermediate tensors
const result = tf.tidy(() => {
const a = tf.tensor([1, 2, 3]);
const b = tf.tensor([4, 5, 6]);
const c = a.add(b);
const d = c.mul(tf.scalar(2));
return d; // Only d is kept
});
// Check memory usage
console.log(tf.memory());
// { numTensors: X, numDataBuffers: Y, numBytes: Z }
Using Pre-Trained Models
The fastest way to get started is using already trained models.
Image Classification With MobileNet
import * as tf from '@tensorflow/tfjs';
import * as mobilenet from '@tensorflow-models/mobilenet';
async function classifyImage() {
// Load the model (first time might take a while)
const model = await mobilenet.load();
// Image element from DOM
const img = document.getElementById('myImage');
// Classify the image
const predictions = await model.classify(img);
console.log(predictions);
// [
// { className: 'golden retriever', probability: 0.89 },
// { className: 'Labrador retriever', probability: 0.08 },
// { className: 'cocker spaniel', probability: 0.02 }
// ]
}Object Detection With COCO-SSD
import * as cocoSsd from '@tensorflow-models/coco-ssd';
async function detectObjects() {
const model = await cocoSsd.load();
const img = document.getElementById('myImage');
const predictions = await model.detect(img);
predictions.forEach(prediction => {
console.log(prediction);
// {
// bbox: [x, y, width, height],
// class: 'person',
// score: 0.95
// }
});
// Draw bounding boxes
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
predictions.forEach(pred => {
ctx.strokeStyle = '#00FF00';
ctx.lineWidth = 2;
ctx.strokeRect(...pred.bbox);
ctx.fillStyle = '#00FF00';
ctx.fillText(
`${pred.class} (${Math.round(pred.score * 100)}%)`,
pred.bbox[0],
pred.bbox[1] - 5
);
});
}
Pose Detection With PoseNet/MoveNet
One of the most popular use cases is body pose detection.
Basic Implementation
import * as poseDetection from '@tensorflow-models/pose-detection';
async function detectPose() {
// Configure detector
const detectorConfig = {
modelType: poseDetection.movenet.modelType.SINGLEPOSE_LIGHTNING
};
const detector = await poseDetection.createDetector(
poseDetection.SupportedModels.MoveNet,
detectorConfig
);
// Use webcam video
const video = document.getElementById('webcam');
async function detect() {
const poses = await detector.estimatePoses(video);
if (poses.length > 0) {
const pose = poses[0];
// Body keypoints
pose.keypoints.forEach(keypoint => {
if (keypoint.score > 0.5) {
console.log(`${keypoint.name}: (${keypoint.x}, ${keypoint.y})`);
// nose: (320, 180)
// left_eye: (310, 170)
// right_shoulder: (280, 250)
// etc.
}
});
}
requestAnimationFrame(detect);
}
detect();
}Practical Application: Exercise Counter
class ExerciseCounter {
constructor() {
this.squatCount = 0;
this.isDown = false;
}
detectSquat(pose) {
const leftHip = pose.keypoints.find(k => k.name === 'left_hip');
const leftKnee = pose.keypoints.find(k => k.name === 'left_knee');
const leftAnkle = pose.keypoints.find(k => k.name === 'left_ankle');
if (!leftHip || !leftKnee || !leftAnkle) return;
// Calculate knee angle
const angle = this.calculateAngle(
leftHip,
leftKnee,
leftAnkle
);
// Counting logic
if (angle < 90 && !this.isDown) {
this.isDown = true;
} else if (angle > 160 && this.isDown) {
this.isDown = false;
this.squatCount++;
this.onSquatComplete(this.squatCount);
}
}
calculateAngle(a, b, c) {
const radians = Math.atan2(c.y - b.y, c.x - b.x) -
Math.atan2(a.y - b.y, a.x - b.x);
let angle = Math.abs(radians * 180 / Math.PI);
if (angle > 180) angle = 360 - angle;
return angle;
}
onSquatComplete(count) {
console.log(`Squat #${count} complete!`);
}
}
Hand Gesture Recognition
Another powerful use case is detecting hand gestures.
import * as handPoseDetection from '@tensorflow-models/hand-pose-detection';
async function detectHands() {
const model = handPoseDetection.SupportedModels.MediaPipeHands;
const detectorConfig = {
runtime: 'tfjs',
maxHands: 2
};
const detector = await handPoseDetection.createDetector(
model,
detectorConfig
);
const video = document.getElementById('webcam');
async function detect() {
const hands = await detector.estimateHands(video);
hands.forEach(hand => {
// 21 keypoints per hand
const landmarks = hand.keypoints;
// Detect "thumbs up" gesture
if (isThumbsUp(landmarks)) {
console.log('👍 Thumbs up detected!');
}
// Detect "peace" gesture
if (isPeaceSign(landmarks)) {
console.log('✌️ Peace sign detected!');
}
});
requestAnimationFrame(detect);
}
detect();
}
function isThumbsUp(landmarks) {
const thumbTip = landmarks[4];
const thumbBase = landmarks[2];
const indexTip = landmarks[8];
// Thumb up and other fingers closed
const thumbUp = thumbTip.y < thumbBase.y;
const indexDown = indexTip.y > landmarks[5].y;
return thumbUp && indexDown;
}
function isPeaceSign(landmarks) {
const indexTip = landmarks[8];
const middleTip = landmarks[12];
const ringTip = landmarks[16];
// Index and middle extended, others closed
const indexUp = indexTip.y < landmarks[5].y;
const middleUp = middleTip.y < landmarks[9].y;
const ringDown = ringTip.y > landmarks[13].y;
return indexUp && middleUp && ringDown;
}
Text Classification With Universal Sentence Encoder
TensorFlow.js isn't just for images - it works great with text.
import * as use from '@tensorflow-models/universal-sentence-encoder';
async function analyzeText() {
const model = await use.load();
// Text embeddings
const sentences = [
'I loved this product, very good!',
'Terrible, do not recommend.',
'The service was ok, nothing exceptional.'
];
const embeddings = await model.embed(sentences);
console.log(embeddings.shape); // [3, 512]
// Simple sentiment classification
const positiveRef = await model.embed(['Excellent, wonderful, perfect']);
const negativeRef = await model.embed(['Terrible, horrible, awful']);
sentences.forEach(async (sentence, i) => {
const sentenceEmbed = embeddings.slice([i, 0], [1, 512]);
const positiveSim = cosineSimilarity(sentenceEmbed, positiveRef);
const negativeSim = cosineSimilarity(sentenceEmbed, negativeRef);
const sentiment = positiveSim > negativeSim ? 'Positive' : 'Negative';
console.log(`"${sentence}" -> ${sentiment}`);
});
}
function cosineSimilarity(a, b) {
const dotProduct = tf.sum(tf.mul(a, b));
const normA = tf.norm(a);
const normB = tf.norm(b);
return dotProduct.div(normA.mul(normB)).dataSync()[0];
}
Training Your Own Model
Besides using pre-trained models, you can create your own.
Simple Classification Model
import * as tf from '@tensorflow/tfjs';
async function trainModel() {
// Example data: XOR problem
const xs = tf.tensor2d([[0, 0], [0, 1], [1, 0], [1, 1]]);
const ys = tf.tensor2d([[0], [1], [1], [0]]);
// Model architecture
const model = tf.sequential({
layers: [
tf.layers.dense({
inputShape: [2],
units: 8,
activation: 'relu'
}),
tf.layers.dense({
units: 4,
activation: 'relu'
}),
tf.layers.dense({
units: 1,
activation: 'sigmoid'
})
]
});
// Compile
model.compile({
optimizer: tf.train.adam(0.1),
loss: 'binaryCrossentropy',
metrics: ['accuracy']
});
// Train
await model.fit(xs, ys, {
epochs: 100,
callbacks: {
onEpochEnd: (epoch, logs) => {
console.log(`Epoch ${epoch}: loss = ${logs.loss.toFixed(4)}`);
}
}
});
// Test
const prediction = model.predict(tf.tensor2d([[0, 1]]));
prediction.print(); // ~0.95 (close to 1)
// Save model
await model.save('localstorage://my-model');
}
// Load saved model
async function loadModel() {
const model = await tf.loadLayersModel('localstorage://my-model');
return model;
}Transfer Learning With MobileNet
async function customImageClassifier() {
// Load MobileNet without final layer
const mobilenet = await tf.loadLayersModel(
'https://tfhub.dev/google/tfjs-model/mobilenet_v2_100_224/feature_vector/3/default/1',
{ fromTFHub: true }
);
// Freeze MobileNet weights
mobilenet.trainable = false;
// Add custom layers
const model = tf.sequential();
model.add(mobilenet);
model.add(tf.layers.dense({
units: 128,
activation: 'relu'
}));
model.add(tf.layers.dropout({ rate: 0.5 }));
model.add(tf.layers.dense({
units: 3, // Number of classes
activation: 'softmax'
}));
model.compile({
optimizer: tf.train.adam(0.0001),
loss: 'categoricalCrossentropy',
metrics: ['accuracy']
});
return model;
}
Performance Optimization
For ML in the browser to work well, optimization is crucial.
WebGL vs CPU Backend
// Check current backend
console.log(tf.getBackend()); // 'webgl' or 'cpu'
// Force WebGL (faster)
await tf.setBackend('webgl');
// Or use WebGPU (even faster, if available)
if (navigator.gpu) {
await tf.setBackend('webgpu');
}Model Quantization
// When converting Python model to TensorFlow.js
// Use quantization to reduce size
// tensorflowjs_converter \
// --input_format=tf_saved_model \
// --output_format=tfjs_graph_model \
// --quantize_float16=* \
// ./saved_model \
// ./tfjs_model
// Result: 50% smaller model, similar performanceModel Warm-up
async function warmupModel(model) {
// First inference is slower
// Do "warm-up" before real use
const dummyInput = tf.zeros([1, 224, 224, 3]);
const warmupResult = model.predict(dummyInput);
warmupResult.dispose();
dummyInput.dispose();
console.log('Model warmed up and ready!');
}
Complete Application: Real-Time Classifier
Let's put everything together in a functional application.
import * as tf from '@tensorflow/tfjs';
import * as mobilenet from '@tensorflow-models/mobilenet';
class RealTimeClassifier {
constructor(videoElement, resultsElement) {
this.video = videoElement;
this.results = resultsElement;
this.model = null;
this.isRunning = false;
}
async init() {
// Configure WebGL
await tf.setBackend('webgl');
// Load model
console.log('Loading model...');
this.model = await mobilenet.load({
version: 2,
alpha: 1.0
});
// Warm up model
await this.warmup();
// Start webcam
await this.setupCamera();
console.log('Ready!');
}
async setupCamera() {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'environment' }
});
this.video.srcObject = stream;
await new Promise(resolve => {
this.video.onloadedmetadata = resolve;
});
this.video.play();
}
async warmup() {
const dummyInput = tf.zeros([1, 224, 224, 3]);
const result = this.model.infer(dummyInput, 'conv_preds');
result.dispose();
dummyInput.dispose();
}
start() {
this.isRunning = true;
this.detect();
}
stop() {
this.isRunning = false;
}
async detect() {
if (!this.isRunning) return;
const predictions = await this.model.classify(this.video, 3);
this.displayResults(predictions);
requestAnimationFrame(() => this.detect());
}
displayResults(predictions) {
this.results.innerHTML = predictions
.map(p => `
<div class="prediction">
<span class="label">${p.className}</span>
<span class="score">${(p.probability * 100).toFixed(1)}%</span>
<div class="bar" style="width: ${p.probability * 100}%"></div>
</div>
`)
.join('');
}
}
// Usage
const classifier = new RealTimeClassifier(
document.getElementById('video'),
document.getElementById('results')
);
document.getElementById('startBtn').onclick = async () => {
await classifier.init();
classifier.start();
};Conclusion
TensorFlow.js democratizes access to machine learning, allowing any JavaScript developer to create intelligent applications without needing server infrastructure or deep Python knowledge.
In 2025, with more powerful GPUs in devices and increasingly optimized models, the browser becomes a legitimate platform for ML - offering privacy, performance, and scale without infrastructure cost.
If you want to explore other ways to improve browser performance, I recommend checking out the article WebAssembly and JavaScript: Extreme Performance where we explore how to combine these technologies.

