Does GSAP Can be adapted for Elementor?

I’m working on a WordPress site using Elementor and I want to add some advanced animations using GSAP. I’ve successfully loaded the GSAP library, but I’m running into issues with responsive behavior and performance.

Current setup:

  • WordPress 6.3
  • Elementor Pro 3.16
  • GSAP 3.12 loaded via CDN

Code I’m using:

gsap.registerPlugin(ScrollTrigger);

gsap.from(".elementor-widget-heading h2", {
  duration: 1,
  y: 50,
  opacity: 0,
  scrollTrigger: ".elementor-widget-heading"
});

Issues I’m facing:

  1. Animations don’t work properly on mobile devices
  2. Sometimes animations trigger multiple times when scrolling back up
  3. Performance seems sluggish on older devices

I’ve seen some impressive implementations on sites like https://laplumerp.com/ or https://studiojae.fr/ where GSAP animations work flawlessly with Elementor. Their animations are smooth and responsive across all devices.

Questions:

  • What’s the best way to ensure GSAP animations are responsive in Elementor?
  • How can I optimize performance for mobile devices?
  • Should I use Elementor’s custom CSS or add scripts in functions.php?
  • Is Any Plugin Can make it easier.

Any help or code examples would be greatly appreciated!

Tags: javascript, wordpress, elementor, gsap, css-animations

I attempted to implement GSAP ScrollTrigger animations on Elementor heading widgets. I loaded GSAP via CDN and added the animation code in Elementor’s custom CSS section.

I expected smooth fade-in animations from bottom to top when elements come into viewport, working consistently across all devices and screen sizes.

LMStudio not connecting to API react

I have built an application in React and suddenly it just won’t connect to LMStudio, no reaction in the logs of the app. Curl requests for models do reply, CORS is turned on and never gave me any issues.

Here is my code:

 const llmModelName = "lmstudio-community/gpt-oss-20b-MXFP4.gguf/"
  const [chatHistory, setChatHistory] = useState([]);
  const [chatInput, setChatInput] = useState('');
  const [isChatting, setIsChatting] = useState(false);

  const handleFetch = () => {
    fetch('http://127.0.0.1:1234/v1/chat/completions')
      .then(response => response.json())
      .then(data => {
        console.log(data);
      })
      .catch(error => {
        console.error('Error fetching data:', error);
      });
  };

messages.push({
  "role": "user",
  "content": userMessageContent
});

try {
  setStatus("Sending to LLM...");
  const response = await fetch(lmStudioApiUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: llmModelName,
      messages: messages,
      temperature: 0.7,
    }),
  });
  if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
  const data = await response.json();
  const llmSummary = data.choices[0].message.content;

  setReport(llmSummary);
  setChatHistory([
    ...messages,
    { "role": "assistant", "content": llmSummary }
  ]);

} catch (error) {
  console.error("Failed to call local LLM API:", error);
  setReport(`Failed to get summary from local LLM. Error: ${error.message}`);
} finally {
  setLoading(false);
  setStatus("Final report ready. You can now chat below.");
  setIsChatting(true);
}
  };

An error occured while loading javascript modules, you may find more information in the devtools console (Odoo)

I overrided the default js method onDownloadButtonClicked (odoo/addons/web/static/src/views/pivot/pivot_render.js) for downloading xlsx files

(it`s my method) statis/src/js/views/my_render.js

/** @odoo-module **/

import { patch } from "@web/core/utils/patch";
import { PivotRenderer } from "@web/views/pivot/pivot_renderer";
import { _t } from "@web/core/l10n/translation";
import { download } from "@web/core/network/download";

patch(PivotRenderer.prototype, {
    onDownloadButtonClicked() {
        if (this.model.getTableWidth() > 16384) {
            throw new Error(
                _t(
                    "For Excel compatibility, data cannot be exported if there are more than 16384 columns.nnTip: try to flip axis, filter further or reduce the number of measures."
                )
            );
        }
        const table = this.model.exportData();
        download({
            url: "/web/pivot/export_xlsx1",  
            data: { data: JSON.stringify(table) },
        });
    },
});

I need this to add a string field to a report generated from a pivot table (it is not possible to add a string field to it directly in views).
Therefore, I wrote my own http.route(export_xlsx1) (copied from the original odoo/addons/web/controllers/pivot.py and modified).

controllers/my_pivot.py

from collections import deque
import io
import json

from werkzeug.datastructures import FileStorage

from odoo import http, _
from odoo.http import content_disposition, request
from odoo.tools import osutil
from odoo.tools.misc import xlsxwriter

from my_import import get_all_picking_notes

class TableExporter(http.Controller):

    @http.route('/web/pivot/export_xlsx1', type='http', auth="user", readonly=True)
    def export_xlsx(self, data, **kw):
        jdata = json.load(data) if isinstance(data, FileStorage) else json.loads(data)
        output = io.BytesIO()
        workbook = xlsxwriter.Workbook(output, {'in_memory': True})
        worksheet = workbook.add_worksheet(jdata['title'])

        header_bold = workbook.add_format({'bold': True, 'pattern': 1, 'bg_color': '#AAAAAA'})
        header_plain = workbook.add_format({'pattern': 1, 'bg_color': '#AAAAAA'})
        bold = workbook.add_format({'bold': True})

        measure_count = jdata['measure_count']
        origin_count = jdata['origin_count']

        # Step 1: writing col group headers
        col_group_headers = jdata['col_group_headers']

        # x,y: current coordinates
        # carry: queue containing cell information when a cell has a >= 2 height
        #      and the drawing code needs to add empty cells below
        x, y, carry = 1, 0, deque()
        for i, header_row in enumerate(col_group_headers):
            worksheet.write(i, 0, '', header_plain)
            for header in header_row:
                while (carry and carry[0]['x'] == x):
                    cell = carry.popleft()
                    for j in range(measure_count * (2 * origin_count - 1)):
                        worksheet.write(y, x+j, '', header_plain)
                    if cell['height'] > 1:
                        carry.append({'x': x, 'height': cell['height'] - 1})
                    x = x + measure_count * (2 * origin_count - 1)
                for j in range(header['width']):
                    worksheet.write(y, x + j, header['title'] if j == 0 else '', header_plain)
                if header['height'] > 1:
                    carry.append({'x': x, 'height': header['height'] - 1})
                x = x + header['width']
            while (carry and carry[0]['x'] == x):
                cell = carry.popleft()
                for j in range(measure_count * (2 * origin_count - 1)):
                    worksheet.write(y, x+j, '', header_plain)
                if cell['height'] > 1:
                    carry.append({'x': x, 'height': cell['height'] - 1})
                x = x + measure_count * (2 * origin_count - 1)
            x, y = 1, y + 1

        # Step 2: writing measure headers
        measure_headers = jdata['measure_headers']
        measure_headers.append({'title': 'My header','width': 1, 'height': 1, 'is_bold': True}) <--custom

        if measure_headers:
            worksheet.write(y, 0, '', header_plain)
            for measure in measure_headers:
                style = header_bold if measure['is_bold'] else header_plain
                worksheet.write(y, x, measure['title'], style)
                for i in range(1, 2 * origin_count - 1):
                    worksheet.write(y, x+i, '', header_plain)
                x = x + (2 * origin_count - 1)
            x, y = 1, y + 1
            # set minimum width of cells to 16 which is around 88px
            worksheet.set_column(0, len(measure_headers), 16)

        # Step 3: writing origin headers
        origin_headers = jdata['origin_headers']

        if origin_headers:
            worksheet.write(y, 0, '', header_plain)
            for origin in origin_headers:
                style = header_bold if origin['is_bold'] else header_plain
                worksheet.write(y, x, origin['title'], style)
                x = x + 1
            y = y + 1

        notes = get_all_picking_notes(request.env) <--custom

        # Step 4: writing data
        x = 0
        for row in jdata['rows']:
            worksheet.write(y, x, f"{row['indent'] * '     '}{row['title']}", header_plain)
            if row['title'] in notes:    <-- custom 
                row['values'].append({'is_bold': True, 'value': notes[row['title']]})
            for cell in row['values']:
                x = x + 1
                if cell.get('is_bold', False):
                    worksheet.write(y, x, cell['value'], bold)
                else:
                    worksheet.write(y, x, cell['value'])
            x, y = 0, y + 1

      
        workbook.close()
        xlsx_data = output.getvalue()
        filename = osutil.clean_filename(_("Pivot %(title)s (%(model_name)s)", title=jdata['title'], model_name=jdata['model']))
        response = request.make_response(xlsx_data,
            headers=[('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
                    ('Content-Disposition', content_disposition(filename + '.xlsx'))],
        )

        return response

In devtools console only one error, and it exists even if i off debug mode (this error occurs even if remove my_render from manifest file)

Content Security Policy of your site blocks the use of 'eval' in JavaScript`  
The Content Security Policy (CSP) prevents the evaluation of arbitrary strings as JavaScript to make it more difficult for an attacker to inject unathorized code on your site.

To solve this issue, avoid using eval(), new Function(), setTimeout([string], ...) and setInterval([string], ...) for evaluating strings.

If you absolutely must: you can enable string evaluation by adding unsafe-eval as an allowed source in a script-src directive.

⚠️ Allowing string evaluation comes at the risk of inline script injection.

1 directive
Source location Directive   Status
script-src  blocked

An error in the client (maybe module conflict or somthing else)

web.assets_web.min.js:26 The following modules could not be loaded because they have unmet dependencies, this is a secondary error which is likely caused by one of the above problems: 
Array(1)
0
: 
"@module_name/js/views/my_render.js"
length
: 
1
[[Prototype]]
: 
Array(0)
at
: 
ƒ at()
concat
: 
ƒ concat()
constructor
: 
ƒ Array()
copyWithin
: 
ƒ copyWithin()
entries
: 
ƒ entries()
every
: 
ƒ every()
fill
: 
ƒ fill()
filter
: 
ƒ filter()
find
: 
ƒ find()
findIndex
: 
ƒ findIndex()
findLast
: 
ƒ findLast()
findLastIndex
: 
ƒ findLastIndex()
flat
: 
ƒ flat()
flatMap
: 
ƒ flatMap()
forEach
: 
ƒ forEach()
includes
: 
ƒ includes()
indexOf
: 
ƒ indexOf()
join
: 
ƒ join()
keys
: 
ƒ keys()
lastIndexOf
: 
ƒ lastIndexOf()
length
: 
0
map
: 
ƒ map()
pop
: 
ƒ pop()
push
: 
ƒ push()
reduce
: 
ƒ reduce()
reduceRight
: 
ƒ reduceRight()
reverse
: 
ƒ reverse()
shift
: 
ƒ shift()
slice
: 
ƒ slice()
some
: 
ƒ some()
sort
: 
ƒ sort()
splice
: 
ƒ splice()
toLocaleString
: 
ƒ toLocaleString()
toReversed
: 
ƒ toReversed()
toSorted
: 
ƒ toSorted()
toSpliced
: 
ƒ toSpliced()
toString
: 
ƒ toString()
unshift
: 
ƒ unshift()
values
: 
ƒ values()
with
: 
ƒ with()
Symbol(Symbol.iterator)
: 
ƒ values()
Symbol(Symbol.unscopables)
: 
{at: true, copyWithin: true, entries: true, fill: true, find: true, …}
[[Prototype]]
: 
Object

Maybe someone know how to fix this problem, or know different method to add strings to pivot value

Rerouting to new page with parameters and fetching from API results in API fetch loop

I made a search panel component, searchPanel.tsx. On it, a button grabs the details from the form (Flight From, Flight To, Flight Date, and some other data), then sends that to a new page.

Here’s the searchPanel.tsx:

'use client';    
import "./searchPanel.css";
import React, { useState } from "react";
import { useRouter } from "next/navigation";

export default function SearchPanel() {
  const router = useRouter();
  const [isUsingFlightNum, setIsUsingFlightNum] = useState(false);
  const [locFromValue, setLocFromValue] = useState('');
  const [locToValue, setLocToValue] = useState('');
  const [flightNumValue, setFlightNumValue] = useState('');
  const [flightDateValue, setFlightDateValue] = useState('');


  const locFromChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setLocFromValue(e.target.value);
  };

  const locToChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setLocToValue(e.target.value);
  };

  const flightNumChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFlightNumValue(e.target.value);
  };

  const flightDateChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setFlightDateValue(e.target.value);
  };

  const handleSubmit = async (formData: FormData) => {
    const flightFrom = formData.get('flightFrom')?.toString() || "";
    const flightTo = formData.get('flightTo')?.toString() || "";
    const flightNumber = formData.get('flightNumber')?.toString() || "";
    const flightDate = formData.get('flightDate')?.toString() || "";

    if (flightDate !== "" && (flightFrom !== "" && flightTo !== "" || flightNumber !== "")) {
      const params = new URLSearchParams();

      params.set('is_using_flight_number', isUsingFlightNum.toString());
      params.set('flight_from', flightFrom);
      params.set('flight_to', flightTo);
      params.set('flight_num', flightNumber);
      params.set('flight_date', flightDate);

      router.push(`/flight/choose?${params.toString()}`)
    } else {
      alert("One or more required fields are missing.");
    }
  }

  return (
    <div>
      Front end code here
    </div>
  );
}

That routes to another page, flight/choose/page.tsx. This page now takes the parameters from the search panel, and uses those as parameters for an API call:

export default async function SelectFlight( {searchParams} : MyPageProps ) {
  // const [selectedFlightCardId, setSelectedFlightCardId] = useState(-1);
  // ^^ Code that I need
  const params = await searchParams;
  const isUsingFlightNum = params!.is_using_flight_number || "";
  const flightFrom = params!.flight_from || "";
  const flightTo = params!.flight_to || "";
  const flightNumber = params!.flight_num || "";
  const flightDate = params!.flight_date || "";

  const url = new URL(`${BASE_API_URL}/api/flights`);
  const options = {
    method: "GET",
  };

  url.searchParams.append('is_using_flight_number', isUsingFlightNum.toString());
  url.searchParams.append('flight_from', flightFrom);
  url.searchParams.append('flight_to', flightTo);
  url.searchParams.append('flight_num', flightNumber);
  url.searchParams.append('flight_date', flightDate);
  const response = await fetch(url, options);
  const result = await response.json();
  const flightData = result.data;

  const handleClick = (event) => {
    const clickedId = event.target.id;
    console.log(`clickedId`, clickedId);
  }

  return (
    <div>
      Front end code here
    </div>
  );
}

This works, until I add "use client" at the very top, which I need for a useState to update a clicked <div> styling and store the ID.

Then the page just repeatedly calls the fetch code, until I kill the project or navigate elsewhere.

I have no idea what to do. Removing my code and simply adding something like a console.log("help") would see help printed repeatedly in the console as well.

How to upload captions with the Youtube API?

No matter what I try, this keeps failing:

await youtube.captions.insert({
    part: ['snippet'],
    media: {
        body: fs.createReadStream('/Users/wannes/Desktop/tmp.srt')
    },
    requestBody: {
        snippet: {
            videoId: "2PRkCBZeihE",
            language: 'nl',
            name: '',
            isDraft: false,
        }
    }
});

Output:

{
    "kind": "youtube#caption",
    "etag": "horsekxpp-GkByDDIDPKuANDDCA",
    "id": "AUieDab4qxyEzvh1AyOlXzJG-2ZSK3TiqwUoRyxyUGfy",
    "snippet": {
        "videoId": "2PRkCBZeihE",
        "lastUpdated": "2025-09-05T08:17:00.002571Z",
        "trackKind": "standard",
        "language": "nl",
        "name": "",
        "audioTrackType": "unknown",
        "isCC": false,
        "isLarge": false,
        "isEasyReader": false,
        "isDraft": false,
        "isAutoSynced": false,
        "status": "failed",
        "failureReason": "processingFailed"
    }
}

Why does my Instagram hashtag copy & paste button not working?

I have one website which has few JavaScript code to copy the Instagram hashtags dynamically, initially it was working, but after sometime it suddenly stopped working. I don’t know the reason; but when I check the console I can see the following error:

parsing error: invalid sfntVersion

symbolCards.forEach(card => {
    card.addEventListener('click', function() {
        const symbol = this.getAttribute('data-symbol');
        
        // Copy to clipboard
        navigator.clipboard.writeText(symbol).then(() => {
            // Show notification
            notification.classList.add('show');
            
            // Hide notification after 2 seconds
            setTimeout(() => {
                notification.classList.remove('show');
            }, 2000);
        }).catch(err => {
            console.error('Failed to copy: ', err);
            alert('Failed to copy symbol. Please try again.');
        });
    });
});

How can I solve this?

Why my insatgram hashtag copy & paste button not working in javascript

i have one website which has few javascript code to copy the instagram hashtags dynamically , initialy it was working but after sometime it sudeely stop working i dont know the reason when i am checking in console it says “parsing error: invalid sfntVersion”, below is the copy code

` symbolCards.forEach(card => {
card.addEventListener(‘click’, function() {
const symbol = this.getAttribute(‘data-symbol’);

                // Copy to clipboard
                navigator.clipboard.writeText(symbol).then(() => {
                    // Show notification
                    notification.classList.add('show');
                    
                    // Hide notification after 2 seconds
                    setTimeout(() => {
                        notification.classList.remove('show');
                    }, 2000);
                }).catch(err => {
                    console.error('Failed to copy: ', err);
                    alert('Failed to copy symbol. Please try again.');
                });
            });`

Site url: https://rb.gy/nga2h1

Please give me a solution to fix it. i have researched all over internet tries muktiple things but could not fix it.

i am looking for a fix to make it work.

driverjs not keeping scroll position in Vuetify v-dialog

I have a Vue 2 project where I’m using both Vuetify and Driver.js. On one page, a v-dialog opens and I use the scrollable attribute. Since the dialog content is very long, it becomes scrollable. Inside the same dialog, I need to highlight certain areas with Driver.js. However, some of these areas are located far down in the content and are only visible after scrolling.

When defining the Driver steps, I use a for loop to dynamically generate them. I create an object like the one below, push it into an array, and then set the steps with setSteps:

let obj = {
  element: item.element ?? null,
  popover: {
    title: item.title ?? '',
    description: item.description ?? '',
    side: 'left',
    align: 'start',
  },
  onHighlightStarted: (element) => {
    element.scrollIntoView({
      behavior: 'smooth',
      block: 'center',
      container: 'nearest'
    });
  },
}

When I reach the elements inside the dialog that require highlighting and need scrolling, the dialog scrolls correctly but then immediately scrolls back to the top on its own. What is the reason for this?

Even when I set smoothScroll: true, I face the same issue.

If I wrap the scrollIntoView function in a setTimeout, the highlighted area appears in the wrong position.

Reminder: Before opening the dialog, the different elements are also shown on the page. After a certain action, the dialog opens and the
steps continue from there.

absolute position div cannot cross the parent’s height when any of the parent’s position is relative

Lets a div has position: relative; If any of the child div has position: absolute, and the height of that absolute positioned div is higher, that div gets clipped by the parent div. I mean, if you use a max-height on any of the parent divs, you need to scroll to go to the end of that position: absolute div. For example: lets see the screenshot.

enter image description here

You can see that a scrollbar has appeared in the accordion-body when opening the dropdown button. But I don’t want the scrollbar there at that event. .dropdown-menu has position: absolute; and .dropdown has position: relative; If we change the .dropdown to position: absolute;, then the issue will be solved, actually (no more scrollbar at the accordion-body). But, for some reason, in the future, if any parent div, for example, .extra-block (a parent) div may have some positioned div and for that we may need to make .extra-block to position: relative, then the issue will appear again. You can also turn the .dropdown-menu to position: fixed;. But then, when you open the dropdown button and scroll the outer body content, the placement of the dropdown breaks. So, is there any good solution?

enter image description here

Demo Fiddle

Fetch the encrypted data into the desired textarea

Fetch the encrypted data into the desired textarea

 <form>
    <textarea name="text" rows="12" cols="70"></textarea><br>
    <input type="text" name="password" size="56" /><br>
    <input type="submit" name="encrypt" value="Enc"><br>
    <textarea name="result" rows="12" cols="70"></textarea><br>
    <input type="submit" name="decrypt" value="Dec"><br>
    <input type="text" name="username">Username<br>
    <input type="text" name="email">Email<br>
    <input type="text" name="pass">Password<br>
  </form>

  <script type="text/javascript">
    $(function() {
      $('input[name=encrypt]').submit(function(event) {
        event.preventDefault();
      });

      $('input[name=encrypt]').click(function(event) {
        try {
          var key = $('input[name=password]').val();
          var text = $('textarea[name=text]').val();

          var bf = new Blowfish(key);

          var res = bf.encrypt(text);


          res = bf.base64Encode(res);
          $('textarea[name=result]').val(res);
        } catch(ex) {
          if (window.console && console.log) {
            console.log(ex)
          }
        }

        return false;
      });
      $('input[name=decrypt]').click(function() {
        try {
          var key = $('input[name=password]').val();
          var text = $('textarea[name=text]').val();

          var bf = new Blowfish(key);

          text = bf.base64Decode(text);

          var res = bf.decrypt(text);
          res = bf.trimZeros(res);

          $('input[name=username]').val(res);
        } catch(ex) {
          if (window.console && console.log) {
            console.log(ex)
          }
        }

        return false;
      });
    });
  </script>

 <form>
    <textarea name="text" rows="12" cols="70"></textarea><br>
    <input type="text" name="password" size="56" /><br>
    <input type="submit" name="encrypt" value="Enc"><br>
    <textarea name="result" rows="12" cols="70"></textarea><br>
    <input type="submit" name="decrypt" value="Dec"><br>
    <input type="text" name="username">Username<br>
    <input type="text" name="email">Email<br>
    <input type="text" name="pass">Password<br>
  </form>

  <script type="text/javascript">
    $(function() {
      $('input[name=encrypt]').submit(function(event) {
        event.preventDefault();
      });

      $('input[name=encrypt]').click(function(event) {
        try {
          var key = $('input[name=password]').val();
          var text = $('textarea[name=text]').val();

          var bf = new Blowfish(key);

          var res = bf.encrypt(text);


          res = bf.base64Encode(res);
          $('textarea[name=result]').val(res);
        } catch(ex) {
          if (window.console && console.log) {
            console.log(ex)
          }
        }

        return false;
      });
      $('input[name=decrypt]').click(function() {
        try {
          var key = $('input[name=password]').val();
          var text = $('textarea[name=text]').val();

          var bf = new Blowfish(key);

          text = bf.base64Decode(text);

          var res = bf.decrypt(text);
          res = bf.trimZeros(res);

          $('input[name=username]').val(res);
        } catch(ex) {
          if (window.console && console.log) {
            console.log(ex)
          }
        }

        return false;
      });
    });
  </script>

When I press the Decrypt button, I want it to write the username, email and password in the text fields.

https://media4.giphy.com/media/v1.Y2lkPTc5MGI3NjExMHhnbmsyeTY5bG0yM2V6aXgzZXhzOHUxOWVqaXBvY29pY2w1aXZmOSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3g3BUQoP36AOXwjQXQ/giphy.gif

There was an error uploading a gif to Stack Overflow. I uploaded it to giphy.com.

Using localStorage for persistent-across-pages dark mode and font selection

I’m trying to use JS (which I am pretty new to) for some display settings on my website. Originally I had checkbox labels and a CSS hack for dark mode and a font swap (the default font is a pixel typeface which I thought some users might have trouble reading), but of course these settings don’t follow the user to another page and have to be re-selected every time.

I’ve been poking around trying to make a script that will use localStorage to keep track of whether a user has changed the palette or font, but I can’t seem to get it to work consistently– it either works once and then reverts on the second page clicked, randomly applies the opposite of the previous state, or does nothing. This is the JS I have (patched together from other people’s code):

//change color scheme on click
function lightMode() {
    let element = document.body;
    element.classList.toggle("light");
    if (element.classList.contains("light")) {
        localStorage.setItem("mode", "light");
    } else {
        localStorage.setItem("mode", "dark");
    }
}

// Set color scheme on pageload
function loadMode() {
    let darkMode = localStorage.getItem("mode");
    if (darkMode === "light") {
        document.body.classList.add("light");
    }
}

window.onload = loadMode;

//change font on click
function textMode() {
    let element = document.body;
    element.classList.toggle("sans");
    if (element.classList.contains("sans")) {
        localStorage.setItem("font", "sans");
    } else {
        localStorage.setItem("font", "pixel");
    }
}

// Set font pref on pageload
function loadFont() {
    let darkMode = localStorage.getItem("font");
    if (darkMode === "sans") {
        document.body.classList.add("sans");
    }
}

window.onload = loadFont;

Here’s the relevant HTML: just links that fire the functions.

 <ul class="drop">
    <li>
       <a onclick="lightMode()" class="lightswitch"> Mode</a>
    </li>
    <li>
       <a onclick="textMode()">Change Font</a>
    </li>
 </ul>

In my CSS is a set of color variables for body, another set for body.light (dark mode is the default), a @font-family spec for body and another one for body.sans.

My guess is that the two onload events are colliding somehow, or I have a name I’m using twice? I have found a bunch of methods for applying a dark mode like this but I haven’t been able to find any that explain how to also manage a second setting. Thanks a bunch for any pointers.

I was expecting to be able to set the color palette and font family on one page, then refresh and/or navigate to another and see the same changes applied. Instead, it is erratically applying one or both styles or sometimes neither. JavaScript and coding in general is a mystery to me.

Why my code behave differently with transferControlToOffscreen or new OffscreenCanvas?

I’m trying to use a web-worker to render a scene with threejs. I want to render some dynamic font using a CanvasTexture. But I find that when I use canvas in web-worker from transferControlToOffscreen, if I change the text, the render loop will stop, but this doesn’t happen with new OffscreenCanvas().

//if use new OffscreenCanvas, render loop will work fine.
//if not, canvas2 is transferd from main thread, when change the text, render loop will stop sometimes
//canvas2=new OffscreenCanvas(200,200);  
canvas2.height = 200;
canvas2.width = 200;
let context = canvas2.getContext('2d');
context.font = "40px Helvetica";
context.fillStyle = 'red';
context.fillText("123", canvas2.width / 2, canvas2.height / 2);
this.sprite = new THREE.Sprite(new THREE.SpriteMaterial({map: new THREE.CanvasTexture(canvas2)}));
this.sprite.scale.set(50, 50, 50);
this.scene.add(this.sprite);

If I declare a canvas in the HTML and simply hide it, after transfered to worker, the problem doesn’t occur.

<canvas id="canvas3"style="display: none;" ></canvas>
// spriteCanvas2 = document.createElement('canvas').transferControlToOffscreen();
spriteCanvas2=document.getElementById('canvas3').transferControlToOffscreen();

The live sample is here.The top canvas run in main thread and the bottom run in worker. If click the bottom canvas, it will stop render sometimes.
Some additional information is in the forum.

If run in local not codepen, it will trigger consistently in three clicks. If not trigger, please refresh the browser.

Could anyone tell the difference between them? Thanks in advance.

How to get enough time from Android to save the local app state to disk?

In our app we listen to the AppState, but even responding immediately and as efficiently as possible in the JS thread, we fail to save the state to disk every time. How are we supposed to get our data saved to disk so a user swiping away from the app quickly won’t result in data loss?

We manage to get all the processing of the state done (which is not much but still work) but that state is never successfully written to the disk. If we just had 2ms that would be plenty, but of course, we apparently don’t have even that much time.

How we listen:

  AppState.addEventListener('change', nextAppState => emit(nextAppState));
  AppState.addEventListener('blur', nextAppState => emit(nextAppState));

Recursively adding properties to object

I need a function that I could call like this:

myFunction("a.b.c.d.e.f", value)

and that will return me an object like this:

{ a: { b: { c: { d: { e: { f: value } } } } } } 

I started doing this:

function myFunction(obj, o) {
    let results = []
      let sp =obj.split('.')
      const firstProperty = sp.shift()
      results[firstProperty] = {}

      if(sp.length > 0) {
        return myFunction(sp.join("."))
      }
      return results
}

but it isn’t working.