As a citizen developer, I am constantly looking for ways to skill up my app making and design game. I recently stumbled upon an article by designer and consultant Tobias Ahlin Bjerrome on adding layered shadows to html elements to give a much nicer look than I had seen before. The basic premise is to add multiple box-shadow elements with increasing offsets and blurs and, optionally, varying the alpha channel of the color to stylize the shadow.
Getting started
So how can we transpose this technique into Power Apps? Let's start by inserting an HTML Text element, named HtmlText1, into an app with a Height of 216, a Width of 217, and PaddingTop, PaddingRight, PaddingBottom, PaddingBottom all set to 0. In the HtmlText property, copy and paste the following:
"<div style='
margin: 8px;
width: " & HtmlText1.Width - 16 & "px;
height: " & HtmlText1.Height - 17 & "px;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.20),
0 2px 2px rgba(0, 0, 0, 0.20),
0 4px 4px rgba(0, 0, 0, 0.20);
border-radius: 10px;
background: #ffffff;'>
</div>"
This will produce the following:
Please note that in these pictures my background color is set to RGBA(240, 240, 240, 1).
Let's break this down a bit. First, the box-shadow is comprised of four elements: the horizontal shadow offset, the vertical shadow offset, the blur radius, and the color (in that order). There are more elements that could be added, which you can explore here, but for this article we will stick with these four. We layer multiples of these box-shadows by adding multiple sets, separating each with a comma and ending with a semicolon.
Next, the margin is set to be twice the largest offset or blur radius, whichever is greater. This keeps the shadow from exceeding the boundaries of the HtmlText and thus cutting the shadow off. The width and height are dynamically set so that they will adjust with the width and height of the whole object. They each have offsets of their own: twice the margin for the width and twice the margin plus one pixel for the height. This ensures that the shadow is centered and fully contained in the HtmlText item.
Last, the border-radius are set to 10px to give rounded corners. Each corner could be set explicitly like so:
border-radius: 10px 10px 10px 10px;
The corners are mapped as follows: Top left, top right, bottom right, and bottom left. These can also be set dynamically as well, for instance referencing the Radius properties of a button object. We also have a background set to #ffffff, or white, to make it easily visible in our example. This can be set dynamically as well or omitted to give a transparent background.
Additionally, the HtmlText height and width are set by design to give a shape that is 200 by 200 pixels. The 216 and 217 we set earlier are the desired height and width plus the offsets mentioned before. These offsets will need to be accounted for when positioning these items as the box-shadow will be offset by the margin on all sides.
Going deeper
This is all well and good for spicing up app design but what if we want to take it a step further? How about we try animating those shadows to provide a sense of motion as well? I have attached a test app where I do just that. If you would like to jump right in and see this technique in action, download that app! I will explain the basics of the technique below but in an effort to keep this article short I will assume that you are using the app as a reference. Also, I made a gallery to reference the relevant control properties and the code therein, allowing for easy copying and pasting.
We will make a button that gives the effect of being physically pressed, in this case a Save button. This button will be comprised of three elements: an HTML text box, an icon, and a Power Apps button. We also have a timer to control the animation. Again, I will omit discussing the construction of these elements for the sake of brevity and just talk about the animation.
The timer's duration will be set to 250. This time gave a decent feel for the animation when clicking the button with a mouse, but feel free to experiment; I didn't put much effort into tuning this aspect. In the OnTimerEnd property of the timer, we'll put:
UpdateContext({varTimerStart: false, varButton: ""})
These variables serve to stop the timer and allow the timer to be used by multiple buttons. The varButton variable is what controls which button animation will be used. We then put varTimerStart in the Start and Reset properties of the timer to give a trigger for starting and resetting the timer for each use.
In the HtmlText property of our shadow element, we set the box-shadow portion as follows:
box-shadow: " & If(
varTimerStart && varButton = "Save",
// Pressed
"0 1px 1px rgba(0, 0, 0, 0.20),
0 2px 2px rgba(0, 0, 0, 0.05),
0 4px 4px rgba(0, 0, 0, 0.0);",
// Unpressed
"0 1px 1px rgba(0, 0, 0, 0.20),
0 2px 2px rgba(0, 0, 0, 0.20),
0 4px 4px rgba(0, 0, 0, 0.20);"
)
This is what allows the box-shadow to change, giving the appearance of pressing. In my example I have this shadow element on the top level of the button, so in the OnSelect property we put:
UpdateContext({varTimerStart: true, varButton: "Save"})
When selecting this button it will start the timer and designate the "Save" button as the button being used, thus allowing the "// Pressed" section of the box-shadow to be used, effectively shortening the shadow. When the timer ends, the varButton variable will be set to "" and the "// Unpressed" section will be used, restoring the shadow to full length.
We are nearly done! There is just one more issue: when "pressing" the button, the icon and text label stay exactly where they are, which looks wrong (at least to me). To help with this, we are going to animate them as well.
In my example app, I pad the icon by 15% of the height. To make the icon bump upward, put the following in the PaddingBottom property:
icoSave.Height * .15 + If(varTimerStart && varButton = "Save", 2, 0)
This bumps the icon up two pixels when the button is pressed. A similar technique is used on the button text. I used the default padding of 5 for that item but in the PaddingBottom we use:
5 + If(varTimerStart && varButton = "Save", 2, 0)
The combination of these three animations give the illusion of motion with the end result looking like so:
Finishing up
And that does it! Utilizing the varButton variable and setting different button names, we can use one timer to control multiple buttons on a screen. While this is a fun technique, I have found that in practice timer controls can be a bit temperamental, resulting in timers that occasionally don't fire and must be reset by deleting and re-adding the control. I've not discovered the reason for this yet but having multiple timers in an app can be mildly frustrating to work with, so proceed at your own emotional risk!
If you do use this technique or adapt it in some amazing way, please reach out and let me know; I'd love to see it!