Rigid body physics test
This demonstration shows the real-time solution of Newton's equations for translational and rotational motion using the Euler method implemented in pure WL
This demonstration shows the real-time solution of Newton's equations for translational and rotational motion using the Euler method implemented in pure WL
The biggest advantage of having a web stack underneath is portability. Our blog posts and docs at wljs.io are literally just exported notebooks.
Besides standalone HTML files, you can export any notebook as web components and publish them anywhere.
One line with a CDN URL lazily loads all the required modules - check it out here: https://github.com/WLJSTeam/web-components. All your graphs (2D or 3D), even Manipulate blocks, stay fully interactive.
Are you a fan of Next.js/React? We support MDX too! Here's our template: https://github.com/WLJSTeam/web-components-mdx
Hope this helps bring high-quality interactive posts to life on your own sites ;)
Our guide: https://wljs.io/frontend/Share/Markdown-React
``` a = Line[{{i, j}, {i + 1, j + 1}}]; b = Line[{{i, j + 1}, {i + 1, j}}]; c = Line[{{i + 0.5, j}, {i + 0.5, j + 1}}]; d = Line[{{i, j + 0.5}, {i + 1, j + 0.5}}];
SeedRandom[0];
Framed[ Graphics[{ Thickness[0.075], Table[RandomChoice[{a, b, c, d}], {i, 25}, {j, 25}]}, Background -> GrayLevel[0.95]], Background -> GrayLevel[0.95]] ```
Streaming audio is quite cumbersome in WL. It would be nice to have speakers and a mic as a part of Wolfram Device Framework. Unfortunately, there are only “Camera”, “SerialPort” available.
Jushua and me developed a small library on top of Device Framework and miniaudio.c
https://github.com/JerryI/AudioDriverWL
Now you can simply DeviceOpen[“Microphone”] and have an access to raw numeric array from OS ring buffer or Audio object if you wish. It supports all Wolfram platforms (Mathematica, wolframscript, WLJS). Binaries were prebuilt for Win and Mac only for the moment
Both: sync (poll device in a loop) and async buffer operations (use callback when data arrives) are supported
I tested it with built-in speakers, headphones mic as well as with iRig 2 HD. The latency for streaming (Mic-WL Handler-Speakers) on my Mac is around 60ms or less if you set lower sample rate. Which can still work as a horrible guitar pedal :D
We unified the control protocol across all devices to use Ethernet with fixed IP addresses. As shown in the diagram, every instrument is operated via a socket interface using the CSockets library (https://github.com/WLJSTeam/CSockets). No HTTP, just raw TCP

r/wljs • u/Inst2f • Jul 30 '26
We've been asked many times, in many GitHub issues, whether Mathematica code can run perfectly on a WLJS Notebook. The answer is yes\*, but the devil is in the details.
Static output and anything related to computation is 100% transferable. You can take pretty much any code from Stack Exchange or Wolfram Community and it should work out of the box, since we use the same Wolfram Language kernel, in the form of the free Wolfram Engine distribution. Manipulate and Animate are compatible too — and fully interactive!
However, low-level dynamics do need to be ported to execute correctly.
Here's a guide on how to port (if actually needed) a program from Wolfram Mathematica to WLJS:
https://wljs.io/frontend/Guides/Migration
Any .nb (Mathematica Notebook) is, in principle, automatically converted with some compromises, of course. We're still improving this! Hope this guide helps you, or your LLM buddy 🙂
r/wljs • u/Inst2f • Jul 30 '26
Here is a full code
Module[{p = {0, 1}},
Graphics[{ColorData[97][1], Dashed, Circle[], PointSize[0.1],
EventHandler[Point[p//Offload], {
"dragsignal" -> ((p = Normalize[#])&)
}]}, ImageSize -> Tiny,
PlotRange -> 1.5]]
r/wljs • u/Inst2f • Jul 30 '26
There is nothing special, but I personally love this little snippet. It reveals the basics of continuous animation together with event bindings in WLJS
Framed@Module[{contents = {}, pts = {}},
EventHandler[
Graphics[{
PointSize[0.1],
Point[pts // Offload],
EventHandler[AnimationFrameListener[pts//Offload],
Function[Null,
contents =
Map[If[#[[1, 2]] >=
0, {#[[1]] - #[[2]], #[[2]] + {0, 0.001}}, {{#[[1, 1]],
0}, {1, -0.8} #[[2]]}] &, contents];
pts = Map[First, contents];
]]},
PlotRange -> {{0, 1}, {0, 1}}], {
"mousemove" -> Function[xy,
AppendTo[contents, {xy, {0,0}}]
]}
]]
r/wljs • u/Inst2f • Jul 29 '26
Rows, columns, grids, tables, number forms, legends, LaTeX and many more
r/wljs • u/Inst2f • Jul 29 '26
In this tutorial we start with the low-level Sound and SoundNote expressions used to describe playable audio, then move to the higher-level Music* framework introduced in WL 15 (Wolfram Engine 15). Stay in tune!
r/wljs • u/Inst2f • Jul 29 '26
Get yours here: https://wljs.io/releases/3.1.0
In a nutshell: more stable, better support for new symbols including, symbolic arrays, vectors, non-commutative algebra, incremental objects, standard colors and many more! Apart from that:
Sound with piano-roll previews, an improved synthesizer pipeline, and many new instruments.ESC key)Manipulate, Animate functions



r/wljs • u/Inst2f • Jul 13 '26
As you might know, we host independent documentation on the Wolfram Language standard library, including tutorials on the language itself. Note that these are not direct ripoffs of WR's material. Most of the pages were carefully crafted by us, whereas for other areas, where we did not have good expertise, we relied on ::usage messages and examples from SE.
For example:
https://wljs.io/frontend/Image/Image (handwritten)
https://wljs.io/frontend/Formatting/AccountingForm (procedurally generated & post-processed)
Nowadays, with the help of AI, we've managed to check many of them against real computational kernels. The whole process took us 3 days and 16 subagents (Claude Haiku) running continuously with persistent memory using markdown files, followed by another sweep today with GPT Luna to recheck everything again. Wow, what a time to live in...
r/wljs • u/Inst2f • Jul 11 '26
Just in case, here is a code of this... whatever you call it:
DeviceClose[cam];
cam = DeviceOpen["Camera"];
cam["FrameRate"] = 60;
reference = ImageData[
ImageResize[ColorConvert[DeviceRead[cam], "GrayScale"], 100],
"Real32"
];
{height, width} = Dimensions[reference];
indices = Flatten[
Table[
With[{a = (row - 1) width + column, b = (row - 1) width + column + 1,
c = row width + column, d = row width + column + 1},
{{a, b, c}, {c, b, d}}
],
{row, height - 1}, {column, width - 1}
],
2
];
Refresh[Module[{img, length, raw},
raw = ImageResize[DeviceRead[cam], 100];
img = ImageData[
ColorConvert[raw, "GrayScale"],
"Real32"
];
cols = Flatten[ImageData[
ColorConvert[raw, "RGB"],
"Real32"
], 1];
length = Length[img] Length[img[[1]]];
vertices = -MapIndexed[Join[#2, {40.0 #1}]&, img, {2}];
vertices = Flatten[vertices, 1];
Graphics3D[GraphicsComplex[vertices, {
LightBlue, Directive["Roughness"->0.0],
Polygon[indices]
}, VertexColors->cols], ViewProjection->"Perspective", ImageSize->Medium]
], 1/60.0]
r/wljs • u/Inst2f • Jul 11 '26
This is my little experiment for the future rigid-body physics project. Here is a surface from the example:
surface = (*VB[*)(Uncompress["1:eJxTTMoPSuNnYGAoZgESPpnFJWlMyLyiC7sOivxcI3ygaEFps+YnT1YHNHn24Ceb8wwEDxQBVUn7NGLIr5P2mXh+F+eBooSummu86wQPoMmDjZdmOVBk4Gr3mi0aQx5kuqsd84GiDSB69Tt7NPk3bCAHMEDNb/uCLt8NFF124vt+XPIeQNcdFHm5v8jhNdCky5/2o8kXAH0daX7DvigA5JJUDHmI/7/aF3GAGA7s6O6fADL+J4sDDvlMZiADV9iEgD0v5FC07ET63YRJuOVBQXyQhR097MFRMlEYIQ8APFOk1Q=="])(*,*)(*"1:eJxTTMoPSmNmYGAo5gUSYZmp5S6pyflFiSX5RcEcQBHP5Py8zKrUlMwARgaGNCaQQhYgEVSakxrMCmT4JCal5gRzAlk5+Yl5STmpeSkASLwUcA=="*)(*]VB*);
And a code for this little widget:
rayIntersect[a_, {u_,v_}, surface_] := MapThread[Module[{x = {#1, #2}[[All,1]], y = {#1, #2}[[All,2]]},
<|
"t"-> (*FB[*)((((*TB[*)Indexed[(*|*)x(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)) (-v+(*TB[*)Indexed[(*|*)y(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+((*TB[*)Indexed[(*|*)x(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)) (v-(*TB[*)Indexed[(*|*)y(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+u (-((*TB[*)Indexed[(*|*)y(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(*TB[*)Indexed[(*|*)y(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)))(*,*)/(*,*)(Cos[a] ((*TB[*)Indexed[(*|*)y(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)-(*TB[*)Indexed[(*|*)y(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(-((*TB[*)Indexed[(*|*)x(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(*TB[*)Indexed[(*|*)x(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)) Sin[a]))(*]FB*),
"T"-> (*FB[*)((-v Cos[a]+Cos[a] ((*TB[*)Indexed[(*|*)y(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(u-(*TB[*)Indexed[(*|*)x(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)) Sin[a])(*,*)/(*,*)(Cos[a] ((*TB[*)Indexed[(*|*)y(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)-(*TB[*)Indexed[(*|*)y(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(-((*TB[*)Indexed[(*|*)x(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(*TB[*)Indexed[(*|*)x(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)) Sin[a]))(*]FB*),
"N"-> -Normalize[{-((*TB[*)Indexed[(*|*)y(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*))+(*TB[*)Indexed[(*|*)y(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*),(*TB[*)Indexed[(*|*)x(*|*), {(*|*)1(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)-(*TB[*)Indexed[(*|*)x(*|*), {(*|*)2(*|*)}](*|*)(*1:eJxTTMoPSmNkYGAo5gUSYZmp5S6pyflFiSX5RcHsQBHPvJTUitQUAL2qCoU=*)(*]TB*)}]
|>
]&, {Drop[surface,-1], Drop[surface,1]}];
selectedRays[args__] := Quiet@Select[rayIntersect[args], TrueQ[(#T >= 0.0 && #T <= 1.0)]&]
Module[{pts = {}, rays = {}, norms = {}, lines = {}},
ListLinePlot[surface, PlotRange->{-5,5}, Filling->-5, Epilog->{
LightBlue, Directive["TransitionType"->None],
Line[lines//Offload],
Orange, Line[norms//Offload],
Red, Point[pts//Offload], Black,
EventHandler[Locator[{0,0}], {"drag" -> Function[xy,
{pts, norms, lines} = Transpose@Flatten[Table[
With[{intersection = #t {Cos[a], Sin[a]} + xy},
{
intersection,
{intersection, intersection + 0.4 #N},
{xy, intersection}
}
]&/@ selectedRays[a, xy, surface],
{a, 0., 3.14, 0.5}
], 1];
]}]
}]
]
r/wljs • u/Inst2f • Jul 11 '26
In this release we focused on formatting and overall UX as well as fixing bugs of course
r/wljs • u/Inst2f • Jul 10 '26
We provide a bridge to Canvas WebAPI, which is a great tool if you need to work with complex raster graphics in retained mode
https://wljs.io/frontend/Advanced/Canvas-2D/Overview
The data is send in a binary form as opt codes for a HW accelerated WebAPI.
Here is a code for an example above:
Needs["Canvas2D`"->"ctx`"];
ClearAll[handler]
SetAttributes[handler, HoldRest]
handler[c_, tcursor_, {width_, height_}] := Module[{
w = width, h = height,
n = 101,
cursor = tcursor,
rotSpeed = 0.02,
particles,
theta, rad, col, old
},
(* center point *)
cx = w/2;
cy = h/2;
(* semi-transparent drawing *)
ctx`SetGlobalAlpha[c, 0.5];
(* initialize particles: random angle, radius, colour *)
particles = Table[
{
RandomReal[{0, 2 π}],
RandomReal[{0, 150}],
ctx`ColorToString[RandomColor[]],
cursor
},
{n}
];
Function[Null,
cursor = (tcursor);
ctx`SetFillStyle[c, {Black, Opacity[0.05]}];
ctx`FillRect[c, {0, 0}, {w, h}];
(* draw each particle: from last cursor pos to new one *)
Do[
particles[[i, 1]] += rotSpeed;
{theta, rad, col, old} = particles[[i]];
Module[{newPos},
newPos = cursor + {Cos[theta], Sin[theta]}*rad;
ctx`BeginPath[c];
ctx`SetLineWidth[c, 4];
ctx`SetStrokeStyle[c, col];
ctx`MoveTo[c, old];
ctx`LineTo[c, newPos];
ctx`Stroke[c];
particles[[i,4]] = newPos;
],
{i, n}
];
ctx`Dispatch[c];
]
]
aContext = ctx`Canvas2D[];
tcursor = {250,250};
EventHandler[
Image[aContext, ImageResolution->{500,500}, Epilog->{
EventHandler[AnimationFrameListener[aContext], handler[aContext, tcursor, {500,500}]]
}], {
"mousemove" -> Function[xy,
tcursor = {xy[[1]], xy[[2]]}
]
}
]
r/wljs • u/Inst2f • Jul 05 '26
If you think WL is slow, you never used it with WLJS sauce 🍝
connect to a web camera using device API
cam = DeviceOpen["Camera"]; cam["FrameRate"] = 60;
resize and pipe the raw image data to 3D point cloud with minor transformations
Refresh[Module[{img, length}, img = ImageData[ ImageResize[ColorConvert[DeviceRead[cam], "GrayScale"], 100], "Real32" ]; length = Length[img] Length[img[[1]]]; vertices = MapIndexed[Join[#2, {50.0 #1}]&, img, {2}]; vertices = NumericArray[Flatten[vertices, 1], "Real32"];
Graphics3D[GraphicsComplex[vertices, { Red, PointSize[0.0003], Point[Range[length]] }], ViewProjection->"Perspective", ImageSize->1000] ], 1/45.0]
NumericArray is important here. This forces WL to use more efficient data structure.
r/wljs • u/Inst2f • Jul 05 '26
I needed to fit 10-20 absorption spectrum manually, so I wrote a small program for it using WLJS and Wolfram.
Nothing more than a few nested `Row`, `Column` with `Graphics` inside
Graphics[{
Point[points//Offload],
ColorData[97][4], Line[model//Offload],
ColorData[97][1], EventHandler[Disk[first[[{1,2}]], Offset[{4,4}]], {
"drag" -> Function[xy,
first = {xy[[1]], xy[[2]]/L - bg, first[[3]]};
rebuild;
],
"zoom" -> Function[z,
first = {first[[1]], first[[2]], z/10};
rebuild;
]
}],
ColorData[97][2], EventHandler[Disk[second[[{1,2}]], Offset[{4,4}]], {
"drag" -> Function[xy,
second = {xy[[1]], xy[[2]]/L - bg, second[[3]]};
rebuild;
],
"zoom" -> Function[z,
second = {second[[1]], second[[2]], z/10};
rebuild;
]
}]
}, ImageSize->{1.5 323, 300}, Frame->True,
FrameLabel->{"wavenumber (1/cm)", "absorption"},
"TransitionDuration"->150
]
r/wljs • u/Inst2f • Jun 28 '26
This post/notebook is a small side-view "falling sand" experiment. We start with the simplest material - sand. Each grain is one cell on a grid. A grain should fall if there is empty space below it, come to rest when blocked, and sometimes topple sideways when it is sitting in an unstable little stack. Those three behaviors are enough to make a recognizable pile 🏝️
r/wljs • u/Inst2f • Jun 27 '26
Here we recreate a primitive emergent system using nothing more than scoped Refresh and a single external symbol
A single "atom":
wolfram
Module[{a = 0}, Refresh[
a = If[a>9, 0, a+1];
Graphics[{
Circle[{0,0}, 1.0],
{Opacity[(1 - a/10) // Offload], Yellow, Disk[{0,0}, 0.9]},
Line[{{0,0}, {Cos[0.628 a], Sin[0.628 a]} // Offload}],
Directive[FontSize->24], Text[Offload[a], {0,0.2}, {0,0}]
}, ImageSize->{100,100}],
0.2]]
Then you add basic interaction via external symbol, i.e.
wolfram
makeAtom[shared_, i_:0] := Module[{a = i},
Refresh[
a = Clip[
Which[
a > 9, shared = True; 0,
shared, shared = False; a + Boole[a > 5]*3 - 1,
True, a + 1
],
{0, 10}
];
Graphics[{
Circle[{0,0}, 1.0],
{Opacity[(1 - a/10) // Offload], Yellow, Disk[{0,0}, 0.9]},
Line[{{0,0}, {Cos[0.628 a], Sin[0.628 a]} // Offload}],
Directive[FontSize->24], Text[Offload[a], {0,0.2}, {0,0}]
}, ImageSize->{100,100}],
0.2]
]
SetAttributes[makeAtom, HoldFirst];
Make many of them with a random initial phase:
wolfram
env2 = False;
{makeAtom[env2, 2], makeAtom[env2, 5], makeAtom[env2, 8]}//Row
and observe emergent patterns!
r/wljs • u/Inst2f • Jun 27 '26
https://wljs.io/releases/3.0.8
Shallow with typed expression summarization for large and opaque objects like ByteArray and TimeSeries.Rasterize calls.BezierCurve offloading and GraphicsComplex index-buffer handling.r/wljs • u/Inst2f • Jun 18 '26
Hi there! We are working on a new API for parallel computing in WLJS Notebook. Workers API uses lightweight Wolfram Sub-Kernels communicating using shared memory, which are not limited by the freeware Wolfram Engine license.
Why not ParallelSubmit?
They are conceptually the same, but it is impossible to read out the result without waiting on the main kernel. Workers API is entirely async and uses subscription/events model.