function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Christian Schwabe (x)Christian Schwabe (x) 

LWC Page Header (smooth transition)

Hi guys,

I am on my way to search for a solution in lwc that is able to provide a smooth transition during scrolling on app page for https://lightningdesignsystem.com/components/page-headers/#Record-Home

It should have the same 'animation' like every hightlight panel on a record page. Is there someone who could help me with that?

What I've already done was translate Record Home to lwc template:
<template>
    <div class="slds-page-header slds-page-header_record-home slds-is-fixed" style="z-index: 99; width: 98%;">
        <div class="slds-page-header__row">
            <div class="slds-page-header__col-title">
                <div class="slds-media">
                    <div class="slds-media__figure">
                        <span class="slds-icon_container">
                            <slot name="icon"></slot>
                        </span>
                    </div>
                    <div class="slds-media__body">
                        <div class="slds-page-header__name">
                            <div class="slds-page-header__name-title">
                                <h1>
                                    <slot name="objectLabelSingular"></slot>
                                    <slot name="recordName" class="slds-page-header__title slds-truncate"></slot>
                                </h1>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
            <template if:true={showPageHeaderActions}>
                <div class="slds-page-header__col-actions">
                    <div class="slds-page-header__controls">
                        <div class="slds-page-header__control">
                            <lightning-button-group>
                                <!-- <template if:true={showButtonGroup}> -->
                                    <template for:each={listOfVisibleButton} for:item="button">
                                        <lightning-button
                                            key={button.name}
                                            name={button.name}
                                            label={button.label}
                                            title={button.title}
                                            icon-name={button.iconName}
                                            onclick={handlePageHeaderActionClicked}
                                        ></lightning-button>
                                    </template>
                                <!-- </template> -->
                                <template if:true={showLightningButtonMenu}>
                                    <lightning-button-menu alternative-text="Show menu" menu-alignment="auto" variant="border-filled">
                                        <template for:each={listOfMenuItem} for:item="menuItem">
                                            <lightning-menu-item
                                                key={menuItem.name}
                                                label={menuItem.label}
                                                value={menuItem.name}
                                                icon-name={menuItem.iconName}
                                            ></lightning-menu-item>
                                        </template>
                                    </lightning-button-menu>
                                </template>
                            </lightning-button-group>
                        </div>
                    </div>
                </div>
            </template>
        </div>
    </div>
</template>

But I stuck on the dynamic part of this scrolling aninmation on my app page. Currently I'm working on show and hide buttons in button-group and move them to lightning-menu-item if you shrink the size of your browser horizontal.

I really appreciate every single help. Thank you.


Best regards,
Chris
Best Answer chosen by Christian Schwabe (x)
mahipal reddymahipal reddy
Hi Christian,
I have developed custom header which mimics salesforce page header behavior. This is not a production ready code but gives you the idea of how to achieve this with LWC.
Repo Link : 
https://github.com/madmahi-sfdc/SalesforceCustomPageHeader

Mahi M

 

All Answers

Alain CabonAlain Cabon
Hello Christian,

FORM_FACTOR is useless here but you can try:  window.addEventListener('resize', this.myFunction);

My test in the playground:

https://developer.salesforce.com/docs/component-library/tools/playground/aytDqETPq/46/edit
 
import { LightningElement, track } from 'lwc';
import FORM_FACTOR from '@salesforce/client/formFactor';

export default class App extends LightningElement {

    myFormFactor = FORM_FACTOR;

    @track showPageHeaderActions = true;

    showButtonGroup = true;

    showLightningButtonMenu = true;

    listOfVisibleButton = [
        {
            name: 'download', label: 'Download',
            title: 'Download action', iconName: 'utility:download'
        },
        {
            name: 'upload', label: 'Upload',
            title: 'Upload action', iconName: 'utility:upload'
        }
    ];

    listOfMenuItem = [{
        name: 'download', label: 'Download',
        title: 'Download action with brand variant', iconName: 'utility:download'
    },
    {
        name: 'upload', label: 'Upload',
        title: 'Upload action with brand variant', iconName: 'utility:upload'
    }];

    connectedCallback() {
        window.addEventListener('resize', this.myFunction);
    }

    myFunction = () => {
        let windowwidth = window.innerWidth;
        console.log(windowwidth);
        if (windowwidth > 500) {
            this.showPageHeaderActions = false;
        } else {
            this.showPageHeaderActions = true;
        }
    };
}

It is perhaps not sufficient if only the size of an inside panel changes while the window has still the same size.
 
Christian Schwabe (x)Christian Schwabe (x)
Hi Alain,

thanks for your response and your hint. My idea goes the same direction: To add / remove buttons from button group to menu-items depending on the width of the component. My question focused more on the vertical transition during scrolling on the whole page. On a record page the hightlight panel (with fields) gets smaller and at the end the fields are hidden and the hightlight panel is sticky at the top of page. Thats what I would like to reach.
Alain CabonAlain Cabon
Ok, you should post an example that shows this behavior ideally in the playground if possible by adding your other fields for seeing the scrollbars and the header shrinking.

I imagine the behavior of a shrinking header indeed but I cannot replicate it with only your posted code.

https://developer.salesforce.com/docs/component-library/tools/playground/aytDqETPq/46/edit

 
Christian Schwabe (x)Christian Schwabe (x)

I've record a short video which shows the default behaviour for the highlight panel: https://www.youtube.com/watch?v=fcgS5eIE0tQ (Look closely to the transition for fields). The fields getting smaller and are hidden at the end and the highlight panel is sticky at the top.

The problem with this
import FORM_FACTOR from '@salesforce/client/formFactor';
is - for my understanding - only working on record page, but not on custom app page.
 
Alain CabonAlain Cabon
Your problem is not the buttons "Edit" "Delete" at all but the fields "Bank Account" "Amount" and so on that diseappear but that is the normal functionality of the header (it is always like that). 

You would like that the fields "Bank Account" "Amount" and so on remain visible while you scroll down?
Christian Schwabe (x)Christian Schwabe (x)
I know it is always like that. I want exactly that but in my custom lwc to use this custom lwc in my own app page. I want that fields are hidden during scrolling and the header is docked at the top of pafe.
Alain CabonAlain Cabon
Ok, you want to mimic the standard header (it is not your LWComponent in the video).

So post your code with your complete header in a trailhead playground and that will help people here (as long as there is no import of third party javascript frameworks). 

I copied/pasted your code above and it is an empty header with just the buttons (almost useless) (that simulates nothing of your problem).

You will never hide the buttons if you mimic the standard header but only the fields below these buttons (that is not your code).
Christian Schwabe (x)Christian Schwabe (x)
I've prepared a playground with all of my necessary code to make everyone able to follow the problem.
Here is the link: https://developer.salesforce.com/docs/component-library/tools/playground/ap-r1MLuH/8/edit

I don't know what should be changed during the onscroll-event. I'm a very beginner to css.
Christian Schwabe (x)Christian Schwabe (x)
I've fixed the errors and saved a new version. 
Alain CabonAlain Cabon
The URL has changed because there are still errors here.

http://​​​​​​​https://developer.salesforce.com/docs/component-library/tools/playground/ap-r1MLuH/8/edit (http://https://developer.salesforce.com/docs/component-library/tools/playground/ap-r1MLuH/8/edit)
Christian Schwabe (x)Christian Schwabe (x)
Sorry. You are right. I've never shared a playground link before and didn't know that the link was changed everytime you click on 'Save.
Here is the link to the new version: https://developer.salesforce.com/docs/component-library/tools/playground/ap-r1MLuH/10/edit
Alain CabonAlain Cabon
Hi Christian,

The current javascript code that rules the behavior of the header and the display of the secondary fields is: highlights2.js

Chrome developer tools:

User-added image


It is not easy to copy/paste it directly for our tests in the playground but that could be tried given that this code is very similar to the code that we used for LWC with probably exceptions that will make the direct use impossible without some changes (I didn't try yet).

https://....lightning.force.com/components/force/highlights2.js

Some used pieces of code below but that is not complete ( it is more complicated by using more functions ) but that shows their internal operations from the beginning of the: o.getScrollPositionSubscriptionEvent(this.onScrollPositionChange.bind(this)
connectedCallback() {
					this[n.CapabilityMixin.ExposeCapabilities]({
						[n.RefreshCapability]: this.refresh.bind(this)
					}),
					this[n.CapabilityMixin.EnableListener](n.RefreshCapability),
					this.dispatchEvent(o.getScrollPositionSubscriptionEvent(this.onScrollPositionChange.bind(this), e => {
							this.scrollPositionSubscriptionDisconnect = e
						}))
				}
				disconnectedCallback() {
					this[n.CapabilityMixin.ConcealCapabilities](),
					this.resizeObserver.disconnect(),
					this.intersectionObserver.disconnect(),
					this.mutationObserver.disconnect(),
					this.destroyAuraActionWrapper(),
					this.scrollPositionSubscriptionDisconnect && this.scrollPositionSubscriptionDisconnect()
				}
				onScrollPositionChange({
					y: e
				}) {
					this.scrollPositionY = e,
					this.calculateShouldEnableCollapse(),
					this.shouldFixAndCollapseHeader && this.transformHeader()
				}
 
onScrollPositionChange({
					y: e
				}) {
					this.scrollPositionY = e,
					this.calculateShouldEnableCollapse(),
					this.shouldFixAndCollapseHeader && this.transformHeader()
				}
 
transformHeader() {
					if (!this.initiallyRendered || !this.shouldFixAndCollapseHeader && !this.collapsed)
						return;
					const t = this.getScrollAmount(),
					i = e.unwrap(this.template.querySelector(C)),
					s = this.template.querySelector(x),
					r = this.collapsed;
					if (!i || void 0 === E)
						return;
					void 0 !== this.originalElementStyles.highlightsContainer.paddingLeft && void 0 !== this.originalElementStyles.highlightsContainer.paddingRight || this.setHighlightsPadding();
					const a = i.clientHeight,
					l = s ? s.clientHeight : 0,
					n = function (e, t) {
						const i = e / (t || E);
						return Math.min(i * E, E)
					}
					(t, l);
					if (t >= 0) {
						this.transformHighlightsContainerElement(i, t, n);
						const e = function (e, t, i) {
							return e ? t : Math.max(i - E, 0)
						}
						(r, l, t);
						if (e <= l) {
							const t = e / l;
							this.transformBodyElements(s, e),
							this.originalElementStyles.highlightsContainer.height ? i.style.height = this.originalElementStyles.highlightsContainer.height - e + "px" : i.style.height = a - l + "px",
							s && (s.style.opacity = 1 - 2 * t)
						} else
							this.transformBodyElements(s, e), i.style.height = this.originalElementStyles.highlightsContainer.height - l + "px", s && (s.style.opacity = 0)
					} else
						this.resetHighlightsStyling(i, s);
					s && (s.style.visibility = s.style.opacity > 0 ? "visible" : "hidden")
				}
 
transformHighlightsContainerElement(e, t, i) {
					const s = function (e) {
						return E - e + "px"
					}
					(i);
					e.style.left = s,
					e.style.right = s,
					e.style.paddingLeft = this.originalElementStyles.highlightsContainer.paddingLeft + i + "px",
					e.style.paddingRight = this.originalElementStyles.highlightsContainer.paddingRight + i + "px",
					e.style.borderRadius = function (e) {
						return e === E ? "0px" : M + "px"
					}
					(i),
					e.style.transform = function (e) {
						const t = Math.min(e, E);
						return t > 0 ? "translate3d(0, -" + t + "px, 0)" : ""
					}
					(t)
				}

The div with class secondaryFields (the fields at the botton) is just hidden.

<div force-highlights2_highlights2="" role="list" class="secondaryFields" style="visibility: hidden; transform: translate3d(0px, -88px, 0px); opacity: 0;">
 
The highlight main panel has minor changes ( fixed position at "left=0,right=0" without border: border-radius: 0px; )

 <div force-highlights2_highlights2="" class="highlights slds-clearfix slds-page-header slds-page-header_record-home fixed-position" style="height: 136.438px; left: 12.8116px; right: 12.8116px; padding-left: 16.1884px; padding-right: 16.1884px; border-radius: 4px; transform: translate3d(0px, -1px, 0px);">

becomes : 

 <div force-highlights2_highlights2="" class="highlights slds-clearfix slds-page-header slds-page-header_record-home fixed-position" style="height: 67.4375px; left: 0px; right: 0px; padding-left: 29px; padding-right: 29px; border-radius: 0px; transform: translate3d(0px, -13px, 0px);">

As you also see, the javascript code is also "obfuscated / simplified".

So by trying to mimic the standard behavior as you begin to do in the playground in a simpler way could be easier with almost the same result.
Alain CabonAlain Cabon
The version plus /edit (/10/edit) at the end seems useless to see the last version directly :   https://developer.salesforce.com/docs/component-library/tools/playground/ap-r1MLuH/

The standard generated page in Lightning for the header of a detail record has a huge amount of tags and CSS classes (much more than the SLDS samples) and that is not usable directly because of the CSS selectors.

This simple collapse of the header is complicated to replicate and I don't know enough the CSS.
Christian Schwabe (x)Christian Schwabe (x)
Hi Alain,

thank for your investigation and your support for this problem. We both have the same problem: Not enough experience in css. That is also my problem. I try in the next couple of days to copy some js-code to my lwc and have a look if I can adapt it. Perphaps in the meanwhile there is something who also investigate in doing this. I didn't believe that I am the first one who try to do this, but I've researched a lot and didn't find any article to it.
Alain CabonAlain Cabon
Hi Christian,

As you also see, the LWC Page Header "smooth transition" is the simple following line of CSS (js minified so difficult to read: Math.min(e, E)?):

e.style.transform = function (e) { const t = Math.min(e, E); return t > 0 ? "translate3d(0, -" + t + "px, 0)" : "" } (t)

The parameter t (ordinate of the translating vector) is not simple to calculate by using the sizes of the components around with event subscriptions to detect the changes in scrolling.

window.onscroll should be sufficient as you used.

I must learn more about these CSS techniques (very used in the underlying lex js) (could be very helpful).
 
Alain CabonAlain Cabon
Hi Christian,

Salesforce doesn't offuscate their javascript code. The minified javascript is reversible in plain readable javascrit with all the comments for the explanations.

Just activate the debugging mode of Salesforce.

René Winkelmeyer explained the technique in details here: https://developer.salesforce.com/blogs/2019/02/debug-your-lightning-web-components.html

 
 
getScrollAmount() {
                    // Get static proxy
                    const proxyElement = this.template.querySelector(PROXY_SELECTOR);
                    // Make the scroll amount 0 in case proxy element is absent

                    if (!proxyElement) {
                        return 0;
                    }
                    // Get header bottom or default to 0

                    const viewportTop = this.viewportTop !== undefined ? this.viewportTop : 0;
                    // Get distance between proxy element and header
                    // This is subtracting a constant value because we need to make this calculation under the
                    // assumption that the highlights is *not* collapsed.
                    // This is for scenarios such as scrolling to collapse, then navigating away, then browser back.
                    // In this scenario, the header should be collapsed.

                    const proxyRect = proxyElement.getBoundingClientRect();
                    const distanceFromViewportTop = proxyRect.top - PAGE_MARGIN;
                    // Return max of (headerBottom minus the distance we are from header) or 0

                    const ret = Math.max(viewportTop - distanceFromViewportTop, 0);
                    return ret;
                }

... still complicated for the getScrollAmount  even if it is readable now.

function calculateSecondaryFieldListTransformStyling(yVector) { return "translate3d(0,-" + yVector + "px,0)"; }

// Transform the highlights container based on the scroll amount and the width expansion amount

this.transformHighlightsContainerElement(highlights, amountScrolledInPixels, widthExpandAmount);
 
transformHighlightsContainerElement(highlights, amountScrolledInPixels, widthExpandAmount) {
                    const reducedSideMargin = calculateReducedSideMargin(widthExpandAmount);
                    highlights.style.left = reducedSideMargin;
                    highlights.style.right = reducedSideMargin;
                    // Expand the side padding based on the amount the width should expand

                    highlights.style.paddingLeft = this.originalElementStyles.highlightsContainer.paddingLeft + widthExpandAmount + 'px';
                    highlights.style.paddingRight = this.originalElementStyles.highlightsContainer.paddingRight + widthExpandAmount + 'px';
                    // Update the border radius of the highlights container to none when we are fully expanded

                    highlights.style.borderRadius = calculateBorderRadius(widthExpandAmount);
                    // Update the transform styling for the highlights container

                    highlights.style.transform = calculateHighlightsContainerTransformStyling(amountScrolledInPixels);
                }

                transformBodyElements(detail, computedScrollAmount) {
                    if (detail) {
                        detail.style.transform = calculateSecondaryFieldListTransformStyling(computedScrollAmount);
                    }
                }
 
function calculateWidthExpansionAmount(amountScrolledInPixels, totalHeightDelta) {
                // Calculate ratio to expand. If totalHeightDelta is 0, use extra margin as the default delta.
                const widthExpandRatio = amountScrolledInPixels / (totalHeightDelta ? totalHeightDelta : PAGE_MARGIN);
                // Calculate the amount to expand the width

                const widthExpandAmount = Math.min(widthExpandRatio * PAGE_MARGIN, PAGE_MARGIN);
                return widthExpandAmount;
            }
            function calculateReducedSideMargin(widthExpandAmount) {
                return PAGE_MARGIN - widthExpandAmount + "px";
            }
            function calculateBorderRadius(widthExpandAmount) {
                return widthExpandAmount === PAGE_MARGIN ? "0px" : BORDER_RADIUS + "px";
            }
            function calculateHighlightsContainerTransformStyling(amountScrolledInPixels) {
                const pixelsToCollapse = Math.min(amountScrolledInPixels, PAGE_MARGIN);
                // Don't set transform attribute if pixelsToCollapse is 0

                return pixelsToCollapse > 0 ? "translate3d(0, -" + pixelsToCollapse + 'px, 0)' : "";
            }
            function calculatedScrollAmount(isCollapsed, totalHeightDelta, amountScrolledInPixels) {
                return isCollapsed ? totalHeightDelta : Math.max(amountScrolledInPixels - PAGE_MARGIN, 0);
            }
            function calculateSecondaryFieldListTransformStyling(yVector) {
                return "translate3d(0,-" + yVector + "px,0)";
            }


The smoth transformation above : calculateSecondaryFieldListTransformStyling

yVector = computedScrollAmount  

// Subtract margin from scroll for scaling calculations since we don't want
// to start scaling until the highlights has reached the top of the page


 const computedScrollAmount = calculatedScrollAmount(collapsedMode, totalHeightDelta, amountScrolledInPixels);

I am going to try to reuse this code. The selectors are not reusable directly.
Christian Schwabe (x)Christian Schwabe (x)
Hi Alain,

thanks for getting back to this post. Unfortunately it's too complicated for my beginner knowledge in javascript. I hope one day someone will provide an open source hightlight panel for LWC and I can reuse it.

Regards,
Chris
mahipal reddymahipal reddy
Hi Christian,
I have developed custom header which mimics salesforce page header behavior. This is not a production ready code but gives you the idea of how to achieve this with LWC.
Repo Link : 
https://github.com/madmahi-sfdc/SalesforceCustomPageHeader

Mahi M

 
This was selected as the best answer
Christian Schwabe (x)Christian Schwabe (x)
Hi Mahipal!

Thank you for your input!