Scale your URCap UI with the zoom level
From PolyScope X 10.15 the user interface is sized in rem, and PolyScope X sets the root font-size of the page per
device. On the TP7 Teach Pendant the UI starts at 135% (a root font-size of 21.6px instead of 16px), so text, icons
and touch targets are larger on its denser display.
Your URCap’s frontend contributions are rendered as custom elements in the same page as PolyScope X. That means:
Sizes in
remin your URCap already follow the zoom level.Sizes in
pxdo not. On a TP7, a URCap styled inpxlooks noticeably smaller than the PolyScope X UI around it.
This guide shows how to make your URCap scale with PolyScope X by converting px to rem when you build, without
rewriting your stylesheets.
Important
Do not set the zoom level from your URCap. Do not set font-size on html, set the --font-base-size CSS variable,
or use CSS zoom or transform: scale() on the page. PolyScope X owns the root font-size, and changing it rescales
all of PolyScope X, not just your URCap. CSS zoom also breaks the positioning of dialog boxes and dropdowns.
How it works
1rem equals the root font-size of the page. PolyScope X sets the root font-size to 16px at 100%, so a stylesheet
where every px value is replaced by px / 16 rem looks exactly the same at 100%, and grows with the zoom level on
larger settings. The conversion runs as a PostCSS step in the Angular build, so you keep writing px in your .scss
files.
There are two parts:
Convert the stylesheets that your own build compiles (step 1).
Convert the component styles of the UI Design Library (
@universal-robots/ui-angular-components), which are shipped already compiled (step 2).
The steps assume the Angular frontend created by the URCap generator, which builds with ngx-build-plus.
Step 1: convert your own styles
Install the PostCSS plugin in your frontend project:
npm install --save-dev postcss-pxtorem
Create postcss.config.json in the root of the frontend project, next to angular.json:
{
"plugins": {
"postcss-pxtorem": {
"rootValue": 16,
"unitPrecision": 5,
"propList": ["*"],
"mediaQuery": false,
"minPixelValue": 2
}
}
}
The Angular build finds this file on its own and runs the plugin on every stylesheet it compiles: component styles,
global styles, and the SCSS of the UI Design Library that you @use.
Option |
Why |
|---|---|
|
16px is 1rem at 100%, so your URCap looks unchanged at the default zoom level. |
|
Convert every property, including custom properties (CSS variables). |
|
Leave |
|
Leave |
Note
The file must be named postcss.config.json (or .postcssrc.json). Angular does not read postcss.config.js.
Step 2: convert the UI Design Library components
The components of the UI Design Library (ur-button, ur-dropdown, ur-tile-button and so on) are shipped in the
npm package as compiled JavaScript with their CSS embedded as strings. Step 1 never sees these styles, so without
step 2 your own layout scales but the ur-* components inside it keep their px sizes.
Note
This step is a workaround for PolyScope X 10.15. If a later version of @universal-robots/ui-angular-components
ships its component styles in rem, the step is no longer needed and you can delete the two files below and the
extraWebpackConfig lines. The workaround is safe to keep either way: values that are already in rem are left
untouched.
Create a build folder in the frontend project with the following two files.
build/vendor-pxtorem-loader.js runs postcss-pxtorem over the component styles embedded in the package:
// Webpack loader: runs postcss-pxtorem over the precompiled component styles embedded in
// Angular packages (the `styles: ["..."]` arrays in their fesm2022 bundles), so the ur-*
// components scale with the PolyScope X zoom level.
// Not needed if your version of @universal-robots/ui-angular-components ships its component styles in rem.
const path = require('path');
const postcss = require('postcss');
const pxtorem = require('postcss-pxtorem');
// Same options as your own stylesheets (postcss.config.json).
const { plugins } = require(path.resolve(__dirname, '../postcss.config.json'));
const processor = postcss([pxtorem(plugins['postcss-pxtorem'])]);
// Angular partial-compilation output: `styles: ["css", "css"]` with double-quoted string literals.
const STYLES_ARRAY = /styles: \[((?:\s*"(?:[^"\\]|\\.)*",?)+)\s*\]/g;
const STRING_LITERAL = /"(?:[^"\\]|\\.)*"/g;
function convert(literal) {
try {
const css = JSON.parse(literal);
return JSON.stringify(processor.process(css, { from: undefined }).css);
} catch {
// Not a plain JSON-compatible string: leave that style unconverted rather than fail the build.
return literal;
}
}
module.exports = function vendorPxToRemLoader(source) {
return source.replace(STYLES_ARRAY, (_, literals) => {
const converted = literals.match(STRING_LITERAL).map(convert);
return `styles: [${converted.join(', ')}]`;
});
};
build/extra-webpack.config.js applies the loader to the UI Design Library, and to ng-select, which ur-dropdown
uses for its option list:
// Merged into the Angular webpack config by ngx-build-plus (angular.json `extraWebpackConfig`).
const path = require('path');
module.exports = {
module: {
rules: [
{
// Runs before Angular's own processing, while the `styles` arrays are still plain strings.
enforce: 'pre',
test: /[\\/](@universal-robots[\\/]ui-angular-components|@ng-select[\\/]ng-select)[\\/]fesm2022[\\/].*\.mjs$/,
use: [path.resolve(__dirname, 'vendor-pxtorem-loader.js')],
},
],
},
};
Register the webpack configuration in angular.json. Add it to the options of both the build target and the serve
target: the dev server reads its own options, not the ones of the build target.
"build": {
"builder": "ngx-build-plus:browser",
"options": {
"singleBundle": true,
"extraWebpackConfig": "build/extra-webpack.config.js",
...
}
},
"serve": {
"builder": "ngx-build-plus:dev-server",
"options": {
"singleBundle": true,
"extraWebpackConfig": "build/extra-webpack.config.js",
...
}
}
Step 3: build and check
Angular keeps a build cache that does not notice changes to postcss.config.json or the loader. Clear it whenever you
add or change either, or some stylesheets stay in px:
rm -rf .angular/cache
npm run build
To check the result, search the built bundle for rem. For example, a 116px width in your .scss becomes 7.25rem.
Sizes the build cannot convert
The conversion only reaches CSS. Check your URCap for these cases, which stay in px and do not scale:
Icons and images sized by their own
widthandheight. An<img>showing an SVG withwidth="60" height="60", or<img width="48" height="48">, keeps that size. Give the image a size in your stylesheet, for example.tile-icon { width: 60px; height: 60px; }, so it is converted and scales.Inline styles.
style="width: 200px"in a template and bindings like[style.width.px]="200"are not converted. Move the size to a stylesheet, or bindremvalues.Sizes computed in TypeScript. Code that measures or positions elements in
pxgets the wrong result at other zoom levels. Work inrem, or read the current root font-size withparseFloat(getComputedStyle(document.documentElement).fontSize).Angular Material. Its component styles are shipped compiled, like the UI Design Library’s, and are not covered by step 2.
1px borders and @media breakpoints are left in px on purpose (see the options in step 1).
Test it
You can preview the TP7 zoom level in the simulator or in any browser. Open the browser developer tools on the PolyScope X page and run:
document.documentElement.style.setProperty('--font-base-size', '21.6');
This sets the root font-size to 21.6px (135%) until the page is reloaded. Check that your URCap now scales like the PolyScope X UI around it, that icons grow with the text, and that dialog boxes and dropdowns still open attached to their fields.
Compatibility
PolyScope X versions before 10.15 use a root font-size of 16px, so a URCap built this way looks the same on them as before. You can ship the same build for all PolyScope X versions.