Skip to content

[FeatureRequest]: native ScrollView in the main process #32751

@ip7e

Description

@ip7e

Problem

Hey Electron Community 👋

We're developing a tab-less browser that allows users to open sites in cards and align them as a grid on an infinite canvas.

Additionally, we intend to create a panoramic scrolling functionality, which allows users to navigate between cards by swiping left and right

The following GIF illustrates a simplified version of the layout and the horizontal scrolling experience:

150981006-84fe796e-60bd-43c0-952d-831d1bfd9998.gif

We use BrowserViews for showing the content of the website.

  • For creating layouts we combined BrowserView.setBounds API together with Facebook YOGA.
  • [Problem]: We want to create scroll functionality. It consists of 2 parts:
    • Capture mouse events
    • Move BrowserViews accordingly left and right, or up and down.

Existing Solution

To capture mouse events we inject custom scripts in the webContents.

It listens to the wheel event on the document and sends deltaX and deltaY to the main process.

On the other side, the main process receives delta value and recalculates the bounds of each BrowserView

// injected script

document.on('wheel', (e) => {
  ipcRenderer.invoke('scroll', e.deltaX)
}, { passive: true })


// main

const views = [ /* collection of browserviews */]

ipcMain.handle('scroll', (e, deltaX) => {
  views.forEach(view => {
    const { x } = view.getBounds()
    view.setBounds({ x: x + deltaX })
  })
})

This approach does not work because of the following reasons.

  • Performance is not smooth enough, especially it suffers on Windows.
  • (for trackpad users) while scrolling whenever the cursor jumps from one BrowserView to another, the document stops to receive wheel events and it gets stuck at the edge. You need to lift the finger from the trackpad and trigger the scroll again. There's no consistency, but it does happen very often. Check out the Demo

Demo

image

The following gist for the electron fiddle also demonstrates the same problem: https://gist.github.com/ddramone/305d9f8fe1a02851beecb407e8b876fb

Proposal

We would like to propose adding an experimental ScrollView and ContainerView APIs in the main process.

//main

const { BaseWindow, ContainerView, ScrollView, BrowserView } = require("electron");

const window = new BaseWindow({ width: 1400, height: 700 })

const scroll = new ScrollView()
scroll.setBounds({ width: 1400, height: 700, x: 0, y: 0 })

const scrollContent = new ContainerView()

let CONTENT_WIDTH = 0


[ /* ... list of cards ... */ ].forEach((card) => {

  const { url, bounds } = card

  const browserView = new BrowserView()

  browserView.webContents.loadURL(url)
  browserView.setBounds(bounds)
  
  scrollContent.addBrowserView(browserView)

  CONTENT_WIDTH += bounds.width

});

scrollContent.setBounds({ width: CONTENT_WIDTH, height: 700, x: 0, y: 0 })

scroll.setContentView(scrollContent);
win.setContainerView(scroll);

This approach allows us to create a scrollable content container by using ContainerView and adding BrowserViews as its children.

Then we add ContainerView as a direct child of the ScrollView which is responsible to capture mouse events and scroll the content accordingly.

How:

In order to achieve the presented assumptions, we have implemented an API that allows building a hierarchy of various types of views. To improve the clarity of the API, We defined ContainerView and ScrollView which inherit from BaseView.

We tried to keep the consistent implementation across all platforms, which is compatible with the Electron architecture - On MacOS we used NSView to support draggable regions, on Windows and Linux we used Chromium's views :: View

We have this approach tested with positive results on both platforms.

Currently, @martin-world1977 is in the process of finalizing the API, testing, and cleaning up changes.

He's the person working on electron full-time.

While we’re preparing pull request, it would be great to hear your opinion.


Shortly About US:

We are Stack and we are building a browser that has focus, organization & collaboration at its core.

Why? Because 2.5b people every day are using a tool (conventional browsers) design of which hasn't changed for more than 20 years. Tab clutter slows us down and we believe the time has come to change that.

We already have a working product that is built using Electron <webview /> and is used by more than 15K people on daily basis. A few months ago, we came across unsolvable technical challenges and decided to rewrite Stack from scratch. After experimenting with various native technologies, we settled back to Electron by using BrowserViews instead of WebViews.

We are a young, growing company with a team of 20 based in Amsterdam, The Netherlands. (www.stackbrowser.com)

We're excited to contribute to the Electron ecosystem and make it even more powerful.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions