Maintain WebView content scroll position on orientation change

The browsers in Android 2.3+ do a good job at maintaining the scrolled position of content on an orientation changed.

I’m trying to achieve the same thing for a WebView which I display within an activity but without success.

  • How to change programmatically the primary color in Android L
  • Cannot lower case button text in android studio
  • How to disable cache in android webview?
  • How can I get the bitmap of the canvas I get in onDraw?
  • How to set padding between ViewPager pages while keeping the page full-width
  • java.lang.RuntimeException: Could not read input channel file descriptors from parcel
  • I’ve tested the manifest change (android:configChanges=”orientation|keyboardHidden”) to avoid activity recreation on a rotation however because of the different aspect ratios the scroll position does not get set to where I want. Furthermore this is not a solution for me as I need to have different layouts in portrait and landscape.

    I’ve tried saving the WebView state and restoring it but this resuls in the content being displayed at the top again.

    Furthermore attempting to scroll in onPageFinished using scrollTo doesn’t work even though the height of the WebView is non-zero at this point.

    Any ideas? Thanks in advance. Peter.

    Partial Solution:

    My colleague managed to get scrolling working via a javascript solution. For simple scrolling to the same vertical position, the WebView’s ‘Y’ scroll position is saved in onSaveInstanceState state. The following is then added to onPageFinished:

    public void onPageFinished(final WebView view, final String url) {
        if (mScrollY > 0) {
            final StringBuilder sb = new StringBuilder("javascript:window.scrollTo(0, ");
            sb.append("/ window.devicePixelRatio);");
        super.onPageFinished(view, url);

    You do get the slight flicker as the content jumps from the beginning to the new scroll position but it is barely noticeable. The next step is try a percentage based method (based on differences in height) and also investigate having the WebView save and restore its state.

    Related posts:

    How can I change the OverScroll color in Android 2.3.1?
    Out of Memory Error ImageView issue
    Android Development: How To Use onKeyUp?
    Where do alpha testers download Google Play Android apps?
    How to get screen resolution of your android device..?
    How to create our own PDF viewer for Android?
  • Android simple spinner item
  • How do you use Android's real time speech to text?
  • How do I add “uses-permissions” tags to AndroidManifest.xml for a Cordova project?
  • Add Server Certificate Information to Trust Manager Android Programmatically
  • install gradle for using in cordova build android
  • understanding mixer_paths.xml in android
  • 8 Solutions collect form web for “Maintain WebView content scroll position on orientation change”

    To restore the current position of a WebView during orientation change I’m afraid you will have to do it manually.

    I used this method:

    1. Calculate actual percent of scroll in the WebView
    2. Save it in the onRetainNonConfigurationInstance
    3. Restore the position of the WebView when it’s recreated

    Because the width and height of the WebView is not the same in portrait and landscape mode, I use a percent to represent the user scroll position.

    Step by step:

    1) Calculate actual percent of scroll in the WebView

    // Calculate the % of scroll progress in the actual web page content
    private float calculateProgression(WebView content) {
        float positionTopView = content.getTop();
        float contentHeight = content.getContentHeight();
        float currentScrollPosition = content.getScrollY();
        float percentWebview = (currentScrollPosition - positionTopView) / contentHeight;
        return percentWebview;

    2) Save it in the onRetainNonConfigurationInstance

    Save the progress just before the orientation change

    public Object onRetainNonConfigurationInstance() {
        OrientationChangeData objectToSave = new OrientationChangeData();
        objectToSave.mProgress = calculateProgression(mWebView);
        return objectToSave;
    // Container class used to save data during the orientation change
    private final static class OrientationChangeData {
        public float mProgress;

    3) Restore the position of the WebView when it’s recreated

    Get the progress from the orientation change data

    private boolean mHasToRestoreState = false;
    private float mProgressToRestore;
    public void onCreate(Bundle savedInstanceState) {
        mWebView = (WebView) findViewById(;
        mWebView.setWebViewClient(new MyWebViewClient());
        OrientationChangeData data = (OrientationChangeData) getLastNonConfigurationInstance();
        if (data != null) {
            mHasToRestoreState = true;
            mProgressToRestore = data.mProgress;

    To restore the current position you will have to wait the page to be reloaded (
    this method can be problematic if your page takes a long time to load)

    private class MyWebViewClient extends WebViewClient {
        public void onPageFinished(WebView view, String url) {
            if (mHasToRestoreState) {
                mHasToRestoreState = false;
                view.postDelayed(new Runnable() {
                    public void run() {
                        float webviewsize = mWebView.getContentHeight() - mWebView.getTop();
                        float positionInWV = webviewsize * mProgressToRestore;
                        int positionY = Math.round(mWebView.getTop() + positionInWV);
                        mWebView.scrollTo(0, positionY);
                // Delay the scrollTo to make it work
                }, 300);
            super.onPageFinished(view, url);

    During my test I encounter that you need to wait a little after the onPageFinished method is called to make the scroll working. 300ms should be ok. This delay make the display to flick (first display at scroll 0 then go to the correct position).

    Maybe there is an other better way to do it but I’m not aware of.

    onPageFinished may not be called because you are not reloading the page, you are just changing the orientation, not sure if this causes a reload or not.

    Try using scrollTo in the onConfigurationChanged method of your activity.

    The aspect change will most likely always cause the current location in your WebView to not be the right location to scroll to afterwards. You could be sneaky and determine the top most visible element in the WebView and after an orientation change implant an anchor at that point in the source and redirect the user to it…

    to calculate the right percentage of WebView, it is important to count mWebView.getScale() also. Actually, return value of getScrollY() is respectively with mWebView.getContentHeight()*mWebView.getScale().

    The way that we have managed to achieve this to have a local reference to the WebView within our Fragments.

    * WebView reference
    private WebView webView;

    Then setRetainInstance to true in onCreate

    public void onCreate(final Bundle savedInstanceState) {
        // Argument loading settings

    Then when onCreateView is called, return the existing local instance of the WebView, otherwise instantiate a new copy setting up the content and other settings. The key step he is when reattaching the WebView to remove the parent which you can see in the else clause below.

    public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
            final Bundle savedInstanceState) {
        final View view;
        if (webView == null) {
            view = inflater.inflate(R.layout.webview_dialog, container);
            webView = (WebView) view.findViewById(;
            // Setup webview and content etc... 
        } else {
            ((ViewGroup) webView.getParent()).removeView(webView);
            view = webView;
        return view;

    I used the partial solution described in the original post, but instead put the javascript snippet inside onPageStarted(), instead of onPageFinished(). Seems to work for me with no jumping.

    My use case is slightly different: I’m trying to keep the horizontal position after the user refreshes the page.

    But I’ve been able to use your solution and it works perfectly for me 🙂

    In the partial solution described in the original post, you can improve the solution if change the following code

    private float calculateProgression(WebView content) {
        float contentHeight = content.getContentHeight();
        float currentScrollPosition = content.getScrollY();
        float percentWebview = currentScrollPosition/ contentHeight;
        return percentWebview;

    due to the top position of the component, when you are using content.getTop () is not necessary; So what it does is add to the actual position of the scroll Y position where the component is located with respect to it parent and runs down the content. I hope my explanation is clear.

    Why you should consider this answer over accepted answer:

    Accepted answer provides decent and simple way to save scroll position, however it is far from perfect. The problem with that approach is that sometimes during rotation you won’t even see any of the elements you saw on the screen before rotation. Element that was at the top of the screen can now be at the bottom after rotation. Saving position via percent of scroll is not very accurate and on large documents this inaccuracy can add up.

    So here is another method: it’s way more complicated, but it almost guarantees that you’ll see exactly the same element after rotation that you saw before rotation. In my opinion, this leads to a much better user experience, especially on a large documents.


    First of all, we will track current scroll position via javascript. This will allow us to know exactly which element is currently at the top of the screen and how much is it scrolled.

    First, ensure that javascript is enabled for your WebView:


    Next, we need to create java class that will accept information from within javascript:

    public class WebScrollListener {
        private String element;
        private int margin;
        public void onScrollPositionChange(String topElementCssSelector, int topElementTopMargin) {
            Log.d("WebScrollListener", "Scroll position changed: " + topElementCssSelector + " " + topElementTopMargin);
            element = topElementCssSelector;
            margin = topElementTopMargin;

    Then we add this class to WebView:

    scrollListener = new WebScrollListener(); // save this in an instance variable
    webView.addJavascriptInterface(scrollListener, "WebScrollListener");

    Now we need to insert javascript code into html page. This script will send scroll data to java (if you are generation html, just append this script; otherwise, you might need to resort to calling document.write() via webView.loadUrl("javascript:document.write(" + script + ")");):

        // We will find first visible element on the screen 
        // by probing document with the document.elementFromPoint function;
        // we need to make sure that we dont just return 
        // body element or any element that is very large;
        // best case scenario is if we get any element that 
        // doesn't contain other elements, but any small element is good enough;
        var findSmallElementOnScreen = function() {
            var SIZE_LIMIT = 1024;
            var elem = undefined;
            var offsetY = 0;
            while (!elem) {
                var e = document.elementFromPoint(100, offsetY);
                if (e.getBoundingClientRect().height < SIZE_LIMIT) {
                    elem = e;
                } else {
                    offsetY += 50;
            return elem;
        // Convert dom element to css selector for later use
        var getCssSelector = function(el) {
            if (!(el instanceof Element)) 
            var path = [];
            while (el.nodeType === Node.ELEMENT_NODE) {
                var selector = el.nodeName.toLowerCase();
                if ( {
                    selector += '#' +;
                } else {
                    var sib = el, nth = 1;
                    while (sib = sib.previousElementSibling) {
                        if (sib.nodeName.toLowerCase() == selector)
                    if (nth != 1)
                        selector += ':nth-of-type('+nth+')';
                el = el.parentNode;
            return path.join(' > ');    
        // Send topmost element and its top offset to java
        var reportScrollPosition = function() {
            var elem = findSmallElementOnScreen();
            if (elem) {
                var selector = getCssSelector(elem);
                var offset = elem.getBoundingClientRect().top;
                WebScrollListener.onScrollPositionChange(selector, offset);
        // We will report scroll position every time when scroll position changes,
        // but timer will ensure that this doesn't happen more often than needed
        // (scroll event fires way too rapidly)
        var previousTimeout = undefined;
        window.addEventListener('scroll', function() {
            previousTimeout = setTimeout(reportScrollPosition, 200);

    If you run your app at this point, you should already see messages in logcat telling you that the new scroll position is received.

    Now we need to save webView state:

    public void onSaveInstanceState(Bundle outState) {
        outState.putString("scrollElement", scrollListener.element);
        outState.putInt("scrollMargin", scrollListener.margin);

    Then we read it in the onCreate (for Activity) or onCreateView (for fragment) method:

    if (savedInstanceState != null) {
        initialScrollElement = savedInstanceState.getString("scrollElement");
        initialScrollMargin = savedInstanceState.getInt("scrollMargin");

    We also need to add WebViewClient to our webView and override onPageFinished method:

    public void onPageFinished(final WebView view, String url) {
        if (initialScrollElement != null) {
            // It's very hard to detect when web page actually finished loading;
            // At the time onPageFinished is called, page might still not be parsed
            // Any javascript inside <script>...</script> tags might still not be executed;
            // Dom tree might still be incomplete;
            // So we are gonna use a combination of delays and checks to ensure
            // that scroll position is only restored after page has actually finished loading
            webView.postDelayed(new Runnable() {
                public void run() {
                    String javascript = "(function ( selectorToRestore, positionToRestore ) {\n" +
                            "       var previousTop = 0;\n" +
                            "       var check = function() {\n" +
                            "           var elem = document.querySelector(selectorToRestore);\n" +
                            "           if (!elem) {\n" +
                            "               setTimeout(check, 100);\n" +
                            "               return;\n" +
                            "           }\n" +
                            "           var currentTop = elem.getBoundingClientRect().top;\n" +
                            "           if (currentTop !== previousTop) {\n" +
                            "               previousTop = currentTop;\n" +
                            "               setTimeout(check, 100);\n" +
                            "           } else {\n" +
                            "               window.scrollBy(0, currentTop - positionToRestore);\n" +
                            "           }\n" +
                            "       };\n" +
                            "       check();\n" +
                            "}('" + initialScrollElement + "', " + initialScrollMargin + "));";
                    webView.loadUrl("javascript:" + javascript);
                    initialScrollElement = null;
            }, 300);

    This is it. After screen rotation, element that was at the top of your screen should now remain there.

    Android Babe is a Google Android Fan, All about Android Phones, Android Wear, Android Dev and Android Games Apps and so on.