Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

[Scriptlet] Fade

1762560232

Edited 1762624262
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
This is a little standalone scriptlet that does one thing. It fades images in and out by incrementally changing their opacity. The controls are simple: Commands !fade --in|<seconds>   !fade --out|<seconds>   !fade --in --all   !fade --out --all   <seconds> = optional fade time (default 1 second).   --all = affects all graphics on the current page.   Examples:   !fade --out           Fade selected to 0% over 1s   !fade --in|3          Fade selected to 100% over 3s   !fade --in|3 --all    Fade in all graphics on page over 3s   Graphics fade over 20 opacity increments but you can edit this in the code if you want it smoother.   All affected graphics update simultaneously.   Code: // Fade — Smooth opacity fading for Roll20 graphics // !fade --in[|time] [--all] // !fade --out[|time] [--all] on('chat:message', (msg) => {     if (msg.type !== 'api' || !msg.content.startsWith('!fade')) return;     const player = getObj('player', msg.playerid); const args = msg.content.trim().split(/\s+--/).slice(1);     const FADE_STEPS = 20; //HIGHER VALUE FOR SMOOTHER FADE     let activeIntervals = [];     // Parse arguments     let fadeIn = false;     let fadeOut = false;     let fadeTime = 1;     let affectAll = false;     args.forEach(arg => {         const [key, value] = arg.split('|');         if (key === 'in') {             fadeIn = true;             fadeTime = value ? parseFloat(value) : 1;         } else if (key === 'out') {             fadeOut = true;             fadeTime = value ? parseFloat(value) : 1;         } else if (key === 'all') {             affectAll = true;         }     });     // Defensive checks     if (!fadeIn && !fadeOut) {         sendChat('Fade', `/w "${player.get('displayname')}" You must specify --in or --out.`);         return;     }     const targetOpacity = fadeIn ? 1.0 : 0.0;     const pageId = player && player.get('lastpage');     if (!affectAll && (!msg.selected || msg.selected.length === 0)) {         sendChat('Fade', `/w "${player.get('displayname')}" No graphics selected. Use --all to affect the entire page.`);         return;     }     if (affectAll && !pageId) {         sendChat('Fade', `/w "${player.get('displayname')}" Could not determine current page.`);         return;     }     // Collect target graphics     let targets = affectAll         ? findObjs({ _pageid: pageId, _type: 'graphic' }) || []         : msg.selected             .map(sel => getObj(sel._type, sel._id))             .filter(obj => obj && obj.get('type') === 'graphic');     if (targets.length === 0) {         sendChat('Fade', `/w "${player.get('displayname')}" No valid graphics found to fade.`);         return;     }     const stepInterval = (fadeTime * 1000) / FADE_STEPS;     // Stop any active fades     activeIntervals.forEach(interval => clearInterval(interval));     activeIntervals = [];     // Precompute fixed per-graphic fade steps     const fadeData = targets.map(g => {         const start = parseFloat(g.get('baseOpacity')) || 0;         const diff = targetOpacity - start;         return {             g,             start,             step: diff / FADE_STEPS,             currentStep: 0         };     }).filter(fd => Math.abs(fd.step) > 0.0001); // skip already at target     if (fadeData.length === 0) return;     const intervalId = setInterval(() => {         let done = true;         fadeData.forEach(fd => {             if (fd.currentStep < FADE_STEPS) {                 const newVal = fd.start + fd.step * (fd.currentStep + 1);                 fd.g.set('baseOpacity', Math.max(0, Math.min(1, newVal)));                 fd.currentStep++;                 done = false;             } else {                 fd.g.set('baseOpacity', targetOpacity);             }         });         if (done) {             clearInterval(intervalId);             activeIntervals = activeIntervals.filter(id => id !== intervalId);         }     }, stepInterval);     activeIntervals.push(intervalId); }); Demo:
1762571833
Gold
Forum Champion
Nice little visual. Why not go ahead and enter the One-Click for greater findability in the long run?
1762618610
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Probably a good idea. I might leave it here for a week or so to gather any feedback, first.
1762624306
keithcurtis
Forum Champion
Marketplace Creator
API Scripter
Example: Nick O just fixed an issue that kept it from working with SelectManager. Thanks, Nick!