js-rich-body-highlighter
v0.1.1
Published
Premium, framework-agnostic muscle map. Highlights muscles with mix-blend-mode over a detailed body illustration for a rich, 3D look instead of flat polygon fills.
Maintainers
Readme
js-rich-body-highlighter
A premium, framework-agnostic muscle map. Instead of flat polygon fills (the
look every other body-highlighter library ships), it highlights muscles with
mix-blend-mode over a detailed body illustration. The color settles into
the grooves between muscle bundles, so each muscle looks lit from within and
three-dimensional rather than painted flat. That richer look is the whole point.
- Framework-agnostic core in plain TypeScript, zero runtime dependencies.
- Wrappers for React, Vue, Svelte, and a
<muscle-map>Web Component (which also covers Angular and anything else). Each framework runtime is an optional peer dependency, so you never pull in one you don't use. - Full body set: male + female, front + back, light + dark, with 14 muscle groups masked on each view.
- Data-driven: highlight by muscle group (or individual mask id), each with its own intensity (0–100) and optional color.
- Adaptive size for free: image and masks share one SVG
viewBox, so sizing the<svg>scales everything together — just give it awidth.
Install
npm install js-rich-body-highlighterUsage
Core (any framework / vanilla)
import { MuscleMap } from 'js-rich-body-highlighter';
const map = new MuscleMap(document.querySelector('#app')!, {
gender: 'female',
view: 'front',
width: 360,
highlights: [
{ group: 'chest', intensity: 70 },
{ group: 'abs', intensity: 45, color: '#22c55e' },
],
onMuscleClick: (muscle) => console.log(muscle.group), // 'chest', 'abs', …
});
map.update({ highlights: [{ group: 'quads', intensity: 60 }] });
map.destroy();React
import { MuscleMap } from 'js-rich-body-highlighter/react';
<MuscleMap
gender="female"
view="back"
width={360}
highlights={[{ group: 'lats', intensity: 70 }]}
onMuscleClick={(muscle) => console.log(muscle.group)}
/>;Vue 3
<script setup lang="ts">
import { MuscleMap } from 'js-rich-body-highlighter/vue';
</script>
<template>
<MuscleMap gender="female" view="back" :width="360"
:highlights="[{ group: 'lats', intensity: 70 }]"
@muscle-click="(m) => console.log(m.group)" />
</template>Svelte (action — no Svelte runtime dependency)
<script lang="ts">
import { muscleMap } from 'js-rich-body-highlighter/svelte';
const options = { gender: 'female', highlights: [{ group: 'chest', intensity: 70 }] };
</script>
<div use:muscleMap={options}></div>Web Component — <muscle-map> (and Angular)
The universal wrapper. Works in plain HTML and in any framework. Simple values
are attributes; complex values (highlights, registry, bodySrc) are
properties; hover/click are DOM CustomEvents.
import 'js-rich-body-highlighter/web-component';
const el = document.querySelector('muscle-map')!;
el.highlights = [{ group: 'chest', intensity: 70 }];
el.addEventListener('muscleclick', (e) => console.log(e.detail.muscle.group));<muscle-map gender="female" view="front" width="360"></muscle-map>In Angular, import the element once and add CUSTOM_ELEMENTS_SCHEMA to your
component/module, then use <muscle-map> in the template and set [highlights]
/ listen to (muscleclick) as usual. (A custom element is Angular's officially
supported way to use non-Angular UI and works in AOT/production builds.)
Highlighting
A highlight targets a group (recommended — most callers think in groups) or a
single mask id, with an intensity of 0–100 and an optional per-entry
color:
highlights: [
{ group: 'chest', intensity: 70 }, // both pecs, red (the default)
{ group: 'biceps', intensity: 50, color: '#22c55e' }, // green
{ id: 'rectus_abdominis_female', intensity: 30 }, // one specific mask
];Targeting by group resolves to every mask of that group in the current
gender + view, so one entry lights up both bodies and both genders. Hovering a
muscle highlights it too (hoverHighlight, hoverIntensity, optional
hoverColor for a distinct tint).
Muscle groups
chest, shoulders, biceps, triceps, forearms, abs, obliques,
upper_back, lats, lower_back, glutes, quads, hamstrings, calves.
Exported as MUSCLE_GROUPS (in head-to-toe order) for building legends, pickers,
or presets.
Options
| Option | Type | Default | Notes |
| ----------------------------------------------- | --------------------------------------------- | ------------- | ----- |
| gender | 'male' \| 'female' | 'male' | picks body + mask set |
| view | 'front' \| 'back' | 'front' | |
| theme | 'light' \| 'dark' | 'light' | swaps body image only |
| width | number \| string | '100%' | px number or CSS string |
| height | number \| string | 'auto' | derived from body aspect ratio |
| highlights | { id?, group?, intensity, color? }[] | [] | intensity 0–100 |
| color | string | '#ff0000' | global highlight color |
| blendMode | string | 'multiply' | any CSS mix-blend-mode |
| hoverHighlight | boolean | true | highlight on hover |
| hoverIntensity | number | 35 | 0–100 |
| hoverColor | string | muscle color | distinct tint for the hovered muscle |
| bodySrc | string \| { front?, back? } | bundled image | bring your own image(s) |
| registry | MuscleDefinition[] | built-in | custom muscle set |
| onMuscleEnter / onMuscleLeave / onMuscleClick | (muscle, event) => void | — | muscle is { id, group, name } |
How it works
- The body is a raster image at
(0,0)inside an<svg>. Each muscle is a<path>traced over it, filled with the highlight color and blended withmix-blend-mode: multiply. Intensity is the path's opacity (0–100 → 0–1). - The
<svg>isisolation: isolate, so the blend only affects the body image, not the page behind it. - Image and masks share one
viewBox(0 0 361.16 541.87, the 1365×2048 body at 96 DPI), so sizing the<svg>scales everything together.
Bundle & images
The 8 bodies are WebP, ~0.66 MB total (≈100 KB each). They are referenced
with new URL('./bodies/<file>', import.meta.url), which modern bundlers
(Vite / webpack 5 / Rollup) emit as separate asset files — not inlined into your
JS bundle. At runtime only the single visible body is fetched (~100 KB), not the
whole set. The JS engine itself is ~30 KB gzipped.
To keep images out of your build entirely (e.g. serve from a CDN), pass
bodySrc (a single URL, or { front, back }); it wins over the bundled default.
