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

Optimizing the HTML of a custom sheet

1731359702
Jiboux
Pro
Sheet Author
Compendium Curator
Hello, With a partner we have been developing over the last years the sheet and a companion API for Earthdawn. System is complex, and we try to support many cases so we ended up with a pretty huge code for HTML, sheetworker and API. The HTML is currently 13.000 lines with 2800 fields to be displayed ( input, select, textarea ...etc) (they are not unique, but there are also the repeating_sections) The Sheetworkers are 15.000 lines more (greatly inflated by pretty large JSONs for some built in data). While the sheet is appreciated by users, it ends up with some display performance issues, especially on low performance computers and/or high-level characters (that have a bigger database due to more repeating items). The problem is loading the sheet when you open it (which in the most extreme cases exceeds one minute, but more average will be 10-20s). We are currently trying to look for ways to optimize, notably by trying to cut some code out and remeasuring (in order to identify optimization potentials), and it is identified that the HTML, more specifically the repeating-sections are big contributors. We want to try to cut down on number of Attributes, but I am also thinking if there is any way to reduce the amount of data that is loaded at the sheet opening. For example in each of our repitems, we may have 8-10 Attributes that are always displayed, but there is maybe 10-15 more that are detail parameters/settings that are only displayed on request. Currently all those are just managed as  display:none  by the CSS until the user clicks somewhere to display them, but I think that at sheet opening the sheet loads a shitload of data that is ultimately hidden. So if I have for example 40 repeating_items, sheet seems to load 40 x (10+15) attributes i.e. 900 attributes, when it will actually only display 400, and will from time to time need 15 more if I open the details of an element. What are recommandations of experimented sheet developper (except "don't have a sheet that big") regarding optimizing the HTML loading ? Are there HTML tags and JS/CSS trickery that you can use to load "just what you need" and then load other chunks of attributes only at the moment they become necessary ?
1731390581

Edited 1731391913
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
The html should have very little effect on sheet load performance. I would check how much you are doing in your sheetworkers in response to the sheet open event. If you have multiple listeners to that event and each one is getting and setting attributes, that will be roughly 100ms of delay per listener. If I recall, you are building an official sheet with a compendium. This means there is no reason to have all that embedded json data. Offload that to the compendium instead of having it hard coded into the sheet. And then, on the subject of the overall sheet size. I don't have anything specific, but I build extremely complex sheets with probably some of the most (if not the most) complex features on Roll20. I have built sheets that run into the 10s of thousands lines of code, however I have found that as I have gotten more experience, a sheet that is 20k lines of html/js is usually a sign that there is a lot of extraneous code. As an example, see the official star finder sheet (20k html+js), which was the first sheet I ever wrote, vs genefunk2090 (6.5k html+js), a sheet i wrote a couple years ago. Both of these sheets have a very similar feature set, but genefunk is less than a third of the starfinder sheet. The reason is genefunk is newer and I had learned how to refactor my code to be more efficient. You can compare both to the walking dead, which is probably the most advanced sheet in the repo but it is almost the same size as genefunk even though it does things that were impossible when the genefunk sheet was written. Using an html/scss template language can help with this because it is easier to see where you are writing more than you need than it is with raw html. And using a pre built sheets infrastructure can also help. However, both can also mask inefficient code, so they have to be used carefully. And the final piece is unintuitive. A great deal of what used to require the API to do can now be done within the sheet. I would recommend moving anything that can possibly be done sheet side from the API to the sheet. In my experience, in order for right sheet-API integration, you wind up needing many extra attributes to allow the sheet and the API script to communicate with each other effectively. Moving that functionality sheet side 1) simplifies your code base, 2) will likely reduce the attributes you require, 3) will probably be faster since you will probably need fewer network calls, and 4) will give all users access to those features.
1731417689
Jiboux
Pro
Sheet Author
Compendium Curator
Hello Scott First thank you for the detailed answer The html should have very little effect on sheet load performance. I would check how much you are doing in your sheetworkers in response to the sheet open event. If you have multiple listeners to that event and each one is getting and setting attributes, that will be roughly 100ms of delay per listener. We actually conducted some measurement to conclude we had to do something with the HTML... On my side, even a very big sheet loads in a semi-reasonable time (10-15s) but my partner was able to build a configuration with a baseline of 90s approx. Removing altogether the code for the Sheetworker had a "marginal" effect (90s=>80s) while removing big chunks of the HTML especially complete repeating_sections had much more effect. Also I was able (maybe it can be help identifying the cause), to identify 2 phases in the HTML loading: Phase 1 : the pop-up window is displayed, with the roll20 native Sheet/Bio etc tabs, but nothing is displayed Phase 2 : The custom sheet has appeared but is not responsive with the D20 loading icon. The Phase using most of the loading time is phase 1. If I recall, you are building an official sheet with a compendium. This means there is no reason to have all that embedded json data. Offload that to the compendium instead of having it hard coded into the sheet. We will run some more measurements, but this was done with the intent to not put the users behind a paywall. In particular we are "servicing" a big community that is on a Westmarch game with above 100 players... Despite many users having the compendium, the limit to 15 for compendium sharing makes that many "entry users" don't have access... Of course if in measurement we find it has a good effect, I'll try to cut in the database, but I would like to keep a minimum to service those users. And then, on the subject of the overall sheet size. I don't have anything specific, but I build extremely complex sheets with probably some of the most (if not the most) complex features on Roll20. I have built sheets that run into the 10s of thousands lines of code, however I have found that as I have gotten more experience, a sheet that is 20k lines of html/js is usually a sign that there is a lot of extraneous code. As an example, see the official star finder sheet (20k html+js), which was the first sheet I ever wrote, vs genefunk2090 (6.5k html+js), a sheet i wrote a couple years ago. Both of these sheets have a very similar feature set, but genefunk is less than a third of the starfinder sheet. The reason is genefunk is newer and I had learned how to refactor my code to be more efficient. You can compare both to the walking dead, which is probably the most advanced sheet in the repo but it is almost the same size as genefunk even though it does things that were impossible when the genefunk sheet was written. Would you mind developping a bit on the type of optimizations you did ? We don't have for restarting the sheet in a different style (I have been several times told about technological debt, but I just don't have a second pandemic ahead of me to start from scratch :D). One of the directions I have been wanting to go in the past is "multiplexing", i.e. we have a lot of different cases and exceptions that are coded each on a single boolean attribute... I'd like to reduce the total attributes by putting them on an HEX instead, and both the API and Sheetworker will work well with that, but we often display on the sheet this with a clickable icon ( a checkbox with a CSS for the icon)... I don't see an easy CSS/HTML to implement that easy clickable icon ( testing only a bit of an hex, changing only a bit from an hex). Using an html/scss template language can help with this because it is easier to see where you are writing more than you need than it is with raw html. And using a pre built sheets infrastructure can also help. However, both can also mask inefficient code, so they have to be used carefully. We are using extensively CSS, but one of the things I am concerned is the quantity of stuff that is loaded but not displayed at a single moment... The sheet has an overview tab, where an overview of each of the repeating sections is shown, and then mostly a tab for each of the repeating_section, where you can actually set the details of each item... For each of the repitems there can be a lot of detailed parameters, so at a given moment there is certainly 80% of the HTML that is loaded but in display:none...  This is where I am thinking there may be something. As said, at this point there is not the dedication to actually start from a clean slate. And the final piece is unintuitive. A great deal of what used to require the API to do can now be done within the sheet. I would recommend moving anything that can possibly be done sheet side from the API to the sheet. In my experience, in order for right sheet-API integration, you wind up needing many extra attributes to allow the sheet and the API script to communicate with each other effectively. Moving that functionality sheet side 1) simplifies your code base, 2) will likely reduce the attributes you require, 3) will probably be faster since you will probably need fewer network calls, and 4) will give all users access to those features. This is something that we have been regularly doing feature by feature... Stuff that we will always keep in the API: - Actual rolling ( we have implemented sequences like attacking / rolling damage / applying damage with single or multiple targets with built-in many of the rules and comparisons that trigger new stuff happening... I don't believe sheetworker can really handle something that involves more than one character) -Complex user interfaces (Queries are not as easy for users with than sending to the chat various buttons to click and choose from) But we keep looking for functions that could be done in the sheet... Thanks a lot for your support and advice
1731441936

Edited 1731466807
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
Jiboux said: The html should have very little effect on sheet load performance. I would check how much you are doing in your sheetworkers in response to the sheet open event. If you have multiple listeners to that event and each one is getting and setting attributes, that will be roughly 100ms of delay per listener. We actually conducted some measurement to conclude we had to do something with the HTML... On my side, even a very big sheet loads in a semi-reasonable time (10-15s) but my partner was able to build a configuration with a baseline of 90s approx. Removing altogether the code for the Sheetworker had a "marginal" effect (90s=>80s) while removing big chunks of the HTML especially complete repeating_sections had much more effect. Also I was able (maybe it can be help identifying the cause), to identify 2 phases in the HTML loading: Phase 1 : the pop-up window is displayed, with the roll20 native Sheet/Bio etc tabs, but nothing is displayed Phase 2 : The custom sheet has appeared but is not responsive with the D20 loading icon. The Phase using most of the loading time is phase 1. I doubt that this is the html itself. If your sheetworkers did not cause a large decrease in the load time, then I would look at your CSS. If you are doing a lot of descendent selectors and generic targeting, this can slow the CSS render engine down (particularly in repeating sections which wind up needing to run the same CSS render multiple times). If I recall, you are building an official sheet with a compendium. This means there is no reason to have all that embedded json data. Offload that to the compendium instead of having it hard coded into the sheet. We will run some more measurements, but this was done with the intent to not put the users behind a paywall. In particular we are "servicing" a big community that is on a Westmarch game with above 100 players... Despite many users having the compendium, the limit to 15 for compendium sharing makes that many "entry users" don't have access... Of course if in measurement we find it has a good effect, I'll try to cut in the database, but I would like to keep a minimum to service those users. Having things in the compendium does not mean that they have to be behind a paywall. Expansion 0 is always available to everyone that uses the compendium regardless of if they have purchased content or not (e.g. the 5e OGL SRD). Additionally, you can designate certain compendium expansions as free to use (e.g. the 5e 2014 playtest rules). And then, on the subject of the overall sheet size. I don't have anything specific, but I build extremely complex sheets with probably some of the most (if not the most) complex features on Roll20. I have built sheets that run into the 10s of thousands lines of code, however I have found that as I have gotten more experience, a sheet that is 20k lines of html/js is usually a sign that there is a lot of extraneous code. As an example, see the official star finder sheet (20k html+js), which was the first sheet I ever wrote, vs genefunk2090 (6.5k html+js), a sheet i wrote a couple years ago. Both of these sheets have a very similar feature set, but genefunk is less than a third of the starfinder sheet. The reason is genefunk is newer and I had learned how to refactor my code to be more efficient. You can compare both to the walking dead, which is probably the most advanced sheet in the repo but it is almost the same size as genefunk even though it does things that were impossible when the genefunk sheet was written. Would you mind developping a bit on the type of optimizations you did ? We don't have for restarting the sheet in a different style (I have been several times told about technological debt, but I just don't have a second pandemic ahead of me to start from scratch :D). One of the directions I have been wanting to go in the past is "multiplexing", i.e. we have a lot of different cases and exceptions that are coded each on a single boolean attribute... I'd like to reduce the total attributes by putting them on an HEX instead, and both the API and Sheetworker will work well with that, but we often display on the sheet this with a clickable icon ( a checkbox with a CSS for the icon)... I don't see an easy CSS/HTML to implement that easy clickable icon ( testing only a bit of an hex, changing only a bit from an hex). There isn't really a way to efficiently multiplex a checkbox. You can have a checkbox that can have multiple values, but you still have to have multiple versions of that checkbox (one for each value, and then all checkboxes have 0 as their unchecked): <input type="checkbox" name="attr_multi_check" value="1"> <input type="checkbox" name="attr_multi_check" value="2"> <input type="checkbox" name="attr_multi_check" value="3"> Of course, checkboxes in Roll20 can still only have a single value assigned to the attribute at a time. If you are wanting to do something like store the values of multiple "attributes" in a single attribute, it isn't something that I would expect to provide any actual benefit. The reason is that you will still need multiple html elements to provide interaction points for each possible value, and the data still has to be saved to the firebase database. Storing the data as individual attributes like: attribute_1: 2 attribute_2: 'some string' attribute_3: '@{my_call}' vs. storing them as JSON in a single attribute: json_attribute: {"attribute_1":2,"attribute_3": "some string", "attribute_3":"@{my_call}"} There's no real performance difference between those two from a network and database standpoint. If anything, the stored JSON would be infinitesimally slower (unnoticeably so unless you were doing this with a MASSIVE dataset) on the client side as you'd have to actually create the JSON for storage and parse the JSON after you had gotten the attribute values to use it. I know you mentioned doing this with data encoded in HEX, but the point holds. There's just very little difference in a project the size of a character sheet between encoded data in a single attribute and that same data unencoded in multiple attributes. I've done multiple speed tests of the sheetworker system and any difference in processing time based on data size is massively outweight by network latency. Regardless of if you are working with a single attribute or 100,000 attributes, the getAttrs and setAttrs calls are going to take the same amount of time (10's of ms and 100's of ms respectively). As for the optimizations that I did, I'm not sure I can quantify them exactly, but they fall into a few broad categories: More efficient CSS. Along with better understanding of CSS, modern CSS has gained many features that were impossible or used to required a lot of convoluted CSS to accomplish. Switching to these once they are available on R20's supported browser allows your CSS to be quicker, smaller, and easier to understand. The above also allows more efficient HTML. I found ways to create the same common constructs that I needed without needing extreme levels of nesting. CSS grid, flexbox, and multi columns are essential for this as they allow extremely flat html constructs to create extremely complex layouts with a minimum of html and CSS code. More efficient Sheetworkers. Inefficient sheetworkers can cause extreme lag on a sheet. Since my first sheet, Starfinder, I've developed and switched to the K-scaffold sheet framework which takes care of my event handling and event cascades for me. This allows my sheetworkers to be extremely compact with lots of reusable code. Something that I never did, but many sheets do make use of is Roll20 autocalculated attributes using the disabled="true" flag on an input to tell Roll20 to calculate the display value from a formula found in the value. These are extremely inefficient and having as few as a hundred of them in a sheet can cause noticeable load lag on a sheet. Note that having them in repeating sections is especially bad because as users add repeating items, you are adding more instances of the auto calc that have to be handled by Roll20. If you are using ANY autocalculated attributes, switch them to sheetworker calculated instead. NOTE: Your descriptions of your sheet's load times matches almost exactly what happens when this is the issue. Using an html/scss template language can help with this because it is easier to see where you are writing more than you need than it is with raw html. And using a pre built sheets infrastructure can also help. However, both can also mask inefficient code, so they have to be used carefully. We are using extensively CSS, but one of the things I am concerned is the quantity of stuff that is loaded but not displayed at a single moment... The sheet has an overview tab, where an overview of each of the repeating sections is shown, and then mostly a tab for each of the repeating_section, where you can actually set the details of each item... For each of the repitems there can be a lot of detailed parameters, so at a given moment there is certainly 80% of the HTML that is loaded but in  display:none...  This is where I am thinking there may be something. As said, at this point there is not the dedication to actually start from a clean slate. I'd take a hard look at your CSS and see if it is overly complex. In my experience this is far more likely to cause load lag than the HTML itself. Of course HTML and CSS complexity go hand in hand. If your HTML is extremely complex and nested, then your CSS will need to be as well. And the final piece is unintuitive. A great deal of what used to require the API to do can now be done within the sheet. I would recommend moving anything that can possibly be done sheet side from the API to the sheet. In my experience, in order for right sheet-API integration, you wind up needing many extra attributes to allow the sheet and the API script to communicate with each other effectively. Moving that functionality sheet side 1) simplifies your code base, 2) will likely reduce the attributes you require, 3) will probably be faster since you will probably need fewer network calls, and 4) will give all users access to those features. This is something that we have been regularly doing feature by feature... Stuff that we will always keep in the API: - Actual rolling ( we have implemented sequences like attacking / rolling damage / applying damage with single or multiple targets with built-in many of the rules and comparisons that trigger new stuff happening... I don't believe sheetworker can really handle something that involves more than one character) -Complex user interfaces (Queries are not as easy for users with than sending to the chat various buttons to click and choose from) But we keep looking for functions that could be done in the sheet... Thanks a lot for your support and advice I would do the actual rolling on the sheet. There are many ways to have sheets affect and react to events on other sheets. You can take a look at the Walking Dead sheet for an example of this in the extreme. The Walking Dead sheet allows NPCs, locations, and factions to be embedded in one another with live updating of their stats across all instances of the given entity regardless of where the change was entered. Similarly, complex user interfaces can be done via the sheet as well. You can output the same chat buttons that you are sending via the API, but have them call back to buttons on the sheet instead of listeners in the API. Additionally, you can pass extra data in those buttons . In many ways, the sheet can react more robustly to a chat button click than the API can. The only thing that the API is required for is if you are doing  1)   complex interactions with the turn tracker (e.g. adding multiple instances of an entity or custom sorting the turn tracker), 2) anything with dynamic lighting/vision, 3) manipulating tokens or other elements on the map, or 4) doing anything with the journal or page list.
1731468473
Jiboux
Pro
Sheet Author
Compendium Curator
I doubt that this is the html itself. If your sheetworkers did not cause a large decrease in the load time, then I would look at your CSS. If you are doing a lot of descendent selectors and generic targeting, this can slow the CSS render engine down (particularly in repeating sections which wind up needing to run the same CSS render multiple times). Very interesting... We do have several descendent selectors... When you inherit via ~ selector does it "memorize" it from one item to the next ? For example we have an attribute attr_editMode, that is outside of the repeating sections, and that used all over to hide/unhide or lock/unlock fields via a descending style. We have a lot of those. I am unsure what you mean by "generic targetting" .sheet-testRepEditMode:checked~.repcontainer .sheet-HideIfEditMode, .sheet-testRepEditMode:not(:checked)~.repcontainer .sheet-HideIfNotEditMode  {  display:    none; }  Having things in the compendium does not mean that they have to be behind a paywall. Expansion 0 is always available to everyone that uses the compendium regardless of if they have purchased content or not (e.g. the 5e OGL SRD). Additionally, you can designate certain compendium expansions as free to use (e.g. the 5e 2014 playtest rules). It's true that is not an avenue that pursued, first because we did this before creating the compendium, but also second because the objective is not like the SRD to give a full access to s subset of the entries, but to give access to all the entries but only with a limited set of data (and in particular to remove all the descriptions.) Creating all of those in an Expansion 0 would mean duplicating every single entry of the compendium with a full/partial version More efficient CSS. Along with better understanding of CSS, modern CSS has gained many features that were impossible or used to required a lot of convoluted CSS to accomplish. Switching to these once they are available on R20's supported browser allows your CSS to be quicker, smaller, and easier to understand. The above also allows more efficient HTML. I found ways to create the same common constructs that I needed without needing extreme levels of nesting. CSS grid, flexbox, and multi columns are essential for this as they allow extremely flat html constructs to create extremely complex layouts with a minimum of html and CSS code. This is very interesting, I'll need to make some research because I learned a lot by doing, so my coding style is pretty archaic... It is really a weakness both in CSS (where I sometimes do a lot of try and error) and JS. I'll have to see maybe recruit a volunteer with better skills than me More efficient Sheetworkers. Inefficient sheetworkers can cause extreme lag on a sheet. Since my first sheet, Starfinder, I've developed and switched to the K-scaffold sheet framework which takes care of my event handling and event cascades for me. This allows my sheetworkers to be extremely compact with lots of reusable code. Yes I remember other posts where you mentioned K-scaffold... As said restarting from scratch on a new framework is above my dedication, but maybe at some point someone will takeover. Something that I never did, but many sheets do make use of is Roll20 autocalculated attributes using the disabled="true" flag on an input to tell Roll20 to calculate the display value from a formula found in the value. These are extremely inefficient and having as few as a hundred of them in a sheet can cause noticeable load lag on a sheet. Note that having them in repeating sections is especially bad because as users add repeating items, you are adding more instances of the auto calc that have to be handled by Roll20. If you are using ANY autocalculated attributes, switch them to sheetworker calculated instead. I have exactly 6 of those in the code, but one is in a repeating_section, so in an average sheet there could be maybe 30 active... From your feedback I'll certainly update to remove the one in repeating_section I would do the actual rolling on the sheet. There are many ways to have sheets affect and react to events on other sheets. You can take a look at the Walking Dead sheet for an example of this in the extreme. The Walking Dead sheet allows NPCs, locations, and factions to be embedded in one another with live updating of their stats across all instances of the given entity regardless of where the change was entered. Similarly, complex user interfaces can be done via the sheet as well. You can output the same chat buttons that you are sending via the API, but have them call back to buttons on the sheet instead of listeners in the API.  Additionally, you can pass extra data in those buttons . In many ways, the sheet can react more robustly to a chat button click than the API can. The only thing that the API is required for is if you are doing 1) complex interactions with the turn tracker (e.g. adding multiple instances of an entity or custom sorting the turn tracker), 2) anything with dynamic lighting/vision, 3) manipulating tokens or other elements on the map, or 4) doing anything with the journal or page list. Very interesting as there are possibilities I didn't suspect. Still several functions that we have that require an API, but giving ideas for more stuff to be transferred: -Synchronizing some repeating_sections with token actions, so that you have always token actions available for your key abilities, and can ultimately play without opening the sheet -Synchronizing token markers and statuses in the sheet -Ability to roll simultaneously multiple tokens with multiple targets Scott Thanks A LOT for the insight... Some stuff to think about !