If a website invites enquiries, its contact action should be easy to find. That does not mean a large button should dominate every moment. On a small screen, a fixed bar can cover the end of the information someone is trying to read. I wanted reading and contacting to work together.
The shared contact-bar source includes behavior that temporarily hides the bar while the reader moves down and restores it after movement stops. Moving upward releases the hidden state; dialogs and overlapping controls are separate conditions. This module extracts and rewrites the core reading behavior without client-specific settings.
An important action does not have to be visible at every moment
I want a visitor to understand the content and then take the next useful action. A permanently visible button can compete with that process if it covers text or interferes with a dialog. Fixed controls occupy a larger share of a mobile screen. I first examine whether the contact treatment is adding a cost to reading.
Removing the action entirely creates another problem: a reader who is ready to enquire must search for it again. Between permanent visibility and disappearance, I used a state that yields during movement and returns afterward. This is an interface choice, not evidence that enquiry rates increased. It separates a reading moment from a contacting moment.
I read direction and idle time as one state
The shared implementation detects downward movement and refreshes a restoration timer. It releases the hidden state when the reader returns upward. Desktop and mobile idle delays can be configured separately, with wheel and touch movement providing additional input. The behavior is easier to explain than simply hiding something because one scroll event arrived.
Small movement matters too. Different downward and upward thresholds can reduce unnecessary flicker. The restoration timer should move forward with continued input, but a tiny movement must not merely clear the timer and leave the bar hidden forever. Deciding that movement has stopped is itself a programmed choice.
I do not leave a keyboard user focused on an invisible control
A control translated outside the viewport has not been removed from the document. If keyboard focus can enter it, it needs to become visible. The rewritten example listens for focusin, restores the bar and prevents scrolling from hiding it while focus remains inside.
I reduce transitions for a person who requests reduced motion. Reserving space below the main content is another requirement: even a correct hide-and-return sequence cannot make the final sentence readable if a fixed bar permanently covers it. I review the relationship between the content and the fixed interface as well as the behavior of the bar.
I make the behavior attachable and removable
The JavaScript example is a new small implementation with client contacts and project-specific classes removed. Reattaching it to the same element cleans up previous listeners, and its returned function removes events and timers. This matters when a single-page interface is redrawn or its configuration changes.
The 430-millisecond delay is a starting value for the example, not a universal optimum. Reading length, bar size and actual scrolling behavior can require a different choice. This core does not include dialog or VR collision checks, touch-input additions or enquiry delivery. I connect and verify those responsibilities separately when they are needed.
Explore the implementation
JavaScript module · a contact bar that yields to reading
const mounted = new WeakMap();
export function attachReadingContactBar(bar, {delay=430, view=window}={}) {
mounted.get(bar)?.();
let lastY=view.scrollY, timer;
const show=()=>bar.classList.remove('is-reading-hidden');
const scheduleShow=()=>{
view.clearTimeout(timer);
timer=view.setTimeout(show,delay);
};
const onScroll=()=>{
const y=view.scrollY;
if(bar.contains(bar.ownerDocument.activeElement))show();
else if(y>lastY+2)bar.classList.add('is-reading-hidden');
else if(y<lastY-8)show();
lastY=y;
scheduleShow();
};
const onFocus=()=>{view.clearTimeout(timer);show();};
view.addEventListener('scroll',onScroll,{passive:true});
bar.addEventListener('focusin',onFocus);
const destroy=()=>{
view.clearTimeout(timer);
view.removeEventListener('scroll',onScroll);
bar.removeEventListener('focusin',onFocus);
show();
if(mounted.get(bar)===destroy)mounted.delete(bar);
};
mounted.set(bar,destroy);
return destroy;
}
I define the motion and content space separately
The module toggles is-reading-hidden; its presentation belongs in CSS. The following treatment includes a fixed position, transition and safe-area spacing. The necessary height changes with button configuration and text size. Copying one padding value does not establish that every screen is safe.
The current website uses email as its only public contact method. I do not place another site’s phone number or recipient inside reusable behavior. Reusing a reading interaction and reusing contact details are different choices. A button that opens a mail composer should not be described as having delivered an enquiry.
Explore the implementation
CSS · motion and bottom content space
.reading-bar {
position: fixed; left: 1rem; right: 1rem; bottom: 1rem;
padding-bottom: env(safe-area-inset-bottom);
transition: transform 180ms ease;
}
.reading-bar.is-reading-hidden { transform: translateY(150%); }
main { padding-bottom: 7rem; }
@media (prefers-reduced-motion: reduce) {
.reading-bar { transition: none; }
}The implementation running on this page
The function described above runs directly in this frame. Scroll inside it to explore hiding and reappearance. Its button changes only the example state and sends no enquiry.
The background reuses this site’s image without identifying information. Code behavior and actual HTTP/API delivery were checked; a new rendered desktop/mobile and browser interaction review of this frame has not been performed.
The sequence I use to verify the choice
Reading the source conditions and checking a browser are different activities. State transitions, reattachment and cleanup in the small implementation can be examined with code. Actual mobile scrolling, focus presentation and visual overlap require the target environment. I do not claim that rendered desktop and mobile review of this new module is complete.
A shorter transition or earlier restoration should serve the context. Hiding a bar briefly does not establish a change in conversion. My first question is whether the reader can continue without obstruction and find the contact route when it becomes useful.
- Does the bar hide during downward movement and return after it stops?
- Is it available when the reader moves upward?
- Does tiny additional movement preserve the return timer?
- Does keyboard focus reveal the control and prevent it from hiding?
- Does reattachment leave only one active behavior?
- Does cleanup remove the events and timers?
- Are the final paragraph and dialog controls unobstructed on the actual screen?