Search code examples
htmlcssflutterwebview-flutter

flutter_webview not scrolling vertically (overflow-y scrollbar not showing)


I'm trying to embed a specific webpage (https://bandcamp.com/EmbeddedPlayer/album=3538525485/size=large/bgcol=ffffff/linkcol=9c7b14/transparent=true/) in Flutter app, but the page is not scrollable for overflown content.

Following is the how it should be shown, notice the scrollable playlist (Screenshot from macOS Safari responsive design mode).

Safari Responsive Design Mode

But in Flutter WebView, currently it is showing as below (Screenshot of iOS simulator):

Flutter WebView iOS Simulator

Notice, although there is content overflowing, but no scrollbar and not able to scroll.

Here's code:

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(const App());
}

class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const CupertinoApp(
      theme: CupertinoThemeData(
        brightness: Brightness.light,
      ),
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  const Home({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text('Home'),
      ),
      child: SafeArea(
        child: WebView(
          initialUrl: 'https://bandcamp.com/EmbeddedPlayer/album=3538525485/size=large/bgcol=ffffff/linkcol=9c7b14/transparent=true/',
          javascriptMode: JavascriptMode.unrestricted,
          zoomEnabled: false,
          gestureRecognizers: {}..add(Factory(() => VerticalDragGestureRecognizer())),
        ),
      ),
    );
  }
}

Any help is much appreciated.


Solution

  • Turns out, I had to embed the Bandcamp player in an iframe.
    After applying some CSS based on LayoutBuilder constraints for responsive layout of embedded player, now everything is working as expected.

    Here's final code:

    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    import 'package:webview_flutter/webview_flutter.dart';
    
    void main() {
      runApp(const App());
    }
    
    class App extends StatelessWidget {
      const App({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return const CupertinoApp(
          theme: CupertinoThemeData(
            brightness: Brightness.light,
          ),
          home: Home(),
        );
      }
    }
    
    class Home extends StatefulWidget {
      const Home({Key? key}) : super(key: key);
    
      @override
      State<Home> createState() => _HomeState();
    }
    
    class _HomeState extends State<Home> {
      int _visibleChildIndex = 0;
    
      @override
      Widget build(BuildContext context) {
        return CupertinoPageScaffold(
          navigationBar: const CupertinoNavigationBar(
            middle: Text(
              'WebView Page',
              overflow: TextOverflow.ellipsis,
            ),
          ),
          child: SafeArea(
            child: IndexedStack(
              index: _visibleChildIndex,
              children: [
                const Center(
                  child: CircularProgressIndicator.adaptive(),
                ),
                Center(
                  child: Column(
                    mainAxisSize: MainAxisSize.max,
                    mainAxisAlignment: MainAxisAlignment.center,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    children: const [
                      Icon(
                        Icons.warning_amber_rounded,
                        size: 64.0,
                      ),
                      SizedBox(height: 8.0),
                      Text(
                        'Failed to load page.',
                        textAlign: TextAlign.center,
                      ),
                    ],
                  ),
                ),
                _buildWebView(
                    'https://bandcamp.com/EmbeddedPlayer/album=3538525485/size=large/bgcol=ffffff/linkcol=9c7b14/transparent=true/'),
              ],
            ),
          ),
        );
      }
    
      String _getBandCampPlayerHTML({
        required String iframeSrc,
        required double width,
        required double height,
      }) {
        return '''
        <!DOCTYPE html>
        <html>
          <head>
            <title>Music Album</title>
            <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
            <style>
              html, body {
                margin: 0;
                padding: 0;
                overflow: hidden;
              }
              .page-container {
                max-width: ${width}px;
                margin: 0 auto;
              }
              .iframe-container {
                width: 0;
                height: 0;
                position: relative;
                padding: ${height}px 0 0 ${width}px;
              }
              iframe {
                position: absolute;
                top: 0;
                left: 0;
                width: 100%;
                height: 100%;
                border: 0;
              }
            </style>
          </head>
          <body>
          <div class="page-container">
            <div class="iframe-container">
              <iframe src="$iframeSrc" scrolling="no" allow="encrypted-media;"></iframe>
            </div>
          </div>
          </body>
        </html> 
        ''';
      }
    
      Widget _buildWebView(String src) {
        return LayoutBuilder(builder: (context, constraints) {
          double width = 700.0;
          final height = constraints.maxHeight;
          width = (constraints.maxWidth > width) ? width : constraints.maxWidth;
          if (height <= width) {
            width = height * 0.6;
          }
          return WebView(
            initialUrl: Uri.dataFromString(
              _getBandCampPlayerHTML(
                iframeSrc: src,
                width: width,
                height: height,
              ),
              mimeType: 'text/html',
            ).toString(),
            javascriptMode: JavascriptMode.unrestricted,
            allowsInlineMediaPlayback: true,
            onWebResourceError: (error) {
              setState(() {
                _visibleChildIndex = 1;
              });
            },
            onPageFinished: (url) {
              setState(() {
                _visibleChildIndex = 2;
              });
            },
          );
        });
      }
    }