diff --git a/Pixiview/App.cs b/Pixiview/App.cs
index ea67e0c..70a006d 100644
--- a/Pixiview/App.cs
+++ b/Pixiview/App.cs
@@ -191,7 +191,7 @@ namespace Pixiview
                                     var distinct = favObj.Illusts.Where(f => !list.Any(i => i.Id == f.Id)).ToList();
                                     list.InsertRange(0, distinct);
 
-                                    favNow.Illusts = list;
+                                    //favNow.Illusts = list;
                                     Stores.SaveFavoritesIllusts();
                                 }
 
diff --git a/Pixiview/Illust/FavoritesPage.xaml.cs b/Pixiview/Illust/FavoritesPage.xaml.cs
index 09b830d..ed8d998 100644
--- a/Pixiview/Illust/FavoritesPage.xaml.cs
+++ b/Pixiview/Illust/FavoritesPage.xaml.cs
@@ -1,6 +1,9 @@
 using System;
 using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
 using Pixiview.Resources;
+using Pixiview.UI;
 using Pixiview.Utils;
 using Xamarin.Essentials;
 using Xamarin.Forms;
@@ -81,9 +84,105 @@ namespace Pixiview.Illust
                 return;
             }
             await ScrollToTopAsync(scrollView);
-            flag = false;
-            lastUpdated = default;
-            StartLoad(true);
+            IsLoading = true;
+
+            var offset = 16 - IndicatorMarginTop;
+            activityLoading.Margin = new Thickness(0, loadingOffset - offset, 0, offset);
+            activityLoading.Animate("margin", top =>
+            {
+                activityLoading.Margin = new Thickness(0, top, 0, offset);
+            },
+            loadingOffset - offset, 16 - offset, easing: Easing.CubicOut, finished: (v, r) =>
+            {
+                Task.Run(() =>
+                {
+                    var list = Stores.LoadOnlineFavorites();
+                    if (list != null && list.Length > 0)
+                    {
+                        MainThread.BeginInvokeOnMainThread(() => ConfirmNext(list));
+                    }
+                    else
+                    {
+                        flag = false;
+                        lastUpdated = default;
+                        MainThread.BeginInvokeOnMainThread(() => StartLoad(true));
+                    }
+                });
+            });
+        }
+
+        private async void ConfirmNext(IllustItem[] list)
+        {
+            var cancel = ResourceHelper.Cancel;
+            var combine = ResourceHelper.FavoritesCombine;
+            var replace = ResourceHelper.FavoritesReplace;
+            var result = await DisplayActionSheet(
+                ResourceHelper.FavoritesOperation,
+                cancel,
+                combine,
+                replace);
+
+            if (result == cancel)
+            {
+                return;
+            }
+            if (result == replace)
+            {
+                Stores.GetFavoriteObject().Illusts = new FavoriteList(list);
+            }
+            else if (result == combine)
+            {
+                // combine
+                var favNow = Stores.GetFavoriteObject();
+                var nows = favNow.Illusts;
+                list = list.Where(f => !nows.Any(i => i.Id == f.Id)).ToArray();
+                for (var i = 0; i < list.Length; i++)
+                {
+                    list[i].Image = StyleDefinition.DownloadBackground;
+                }
+                nows.InsertRange(0, list);
+            }
+            else
+            {
+                return;
+            }
+
+            ParallelTask.Start(0, list.Length, Configs.MaxPageThreads, i =>
+            {
+                var illustItem = list[i];
+                var data = Stores.LoadIllustPreloadData(illustItem.Id);
+                if (data != null && data.illust.TryGetValue(illustItem.Id, out var illust))
+                {
+                    illust.CopyToItem(illustItem);
+                    if (data.user.TryGetValue(illust.userId, out var user))
+                    {
+                        illustItem.ProfileUrl = user.image;
+                    }
+                    return true;
+                }
+                App.DebugError("load.favorite", $"cannot fetch preload data, {illustItem.Id}, disposed.");
+                return false;
+            }, () =>
+            {
+                Stores.SaveFavoritesIllusts();
+
+                MainThread.BeginInvokeOnMainThread(() =>
+                {
+                    var offset = 16 - IndicatorMarginTop;
+                    activityLoading.Animate("margin", top =>
+                    {
+                        activityLoading.Margin = new Thickness(0, top, 0, offset);
+                    },
+                    16 - offset, loadingOffset - offset, easing: Easing.CubicIn, finished: (v, r) =>
+                    {
+                        IsLoading = false;
+
+                        flag = false;
+                        lastUpdated = default;
+                        StartLoad();
+                    });
+                });
+            });
         }
 
         private async void ShareFavorites_Clicked(object sender, EventArgs e)
diff --git a/Pixiview/Illust/ViewIllustPage.xaml.cs b/Pixiview/Illust/ViewIllustPage.xaml.cs
index 2220c28..cc3e16f 100644
--- a/Pixiview/Illust/ViewIllustPage.xaml.cs
+++ b/Pixiview/Illust/ViewIllustPage.xaml.cs
@@ -514,6 +514,11 @@ namespace Pixiview.Illust
             }
             else
             {
+                var item = favorites[index];
+                if (illust.BookmarkId == null && item.BookmarkId != null)
+                {
+                    illust.BookmarkId = item.BookmarkId;
+                }
                 illust.IsFavorite = false;
                 favorites.RemoveAt(index);
                 FavoriteIcon = fontIconNotLove;
diff --git a/Pixiview/Utils/Extensions.cs b/Pixiview/Utils/Extensions.cs
index 4a8f695..fb982e2 100644
--- a/Pixiview/Utils/Extensions.cs
+++ b/Pixiview/Utils/Extensions.cs
@@ -95,13 +95,13 @@ namespace Pixiview.Utils
 
     public class ParallelTask : IDisposable
     {
-        public static ParallelTask Start(int from, int toExclusive, int maxCount, Predicate<int> action)
+        public static ParallelTask Start(int from, int toExclusive, int maxCount, Predicate<int> action, Action complete = null)
         {
             if (toExclusive <= from)
             {
                 return null;
             }
-            var task = new ParallelTask(from, toExclusive, maxCount, action);
+            var task = new ParallelTask(from, toExclusive, maxCount, action, complete);
             Task.Run(task.Start);
             return task;
         }
@@ -114,8 +114,9 @@ namespace Pixiview.Utils
         private readonly int from;
         private readonly int to;
         private readonly Predicate<int> action;
+        private readonly Action complete;
 
-        private ParallelTask(int from, int to, int maxCount, Predicate<int> action)
+        private ParallelTask(int from, int to, int maxCount, Predicate<int> action, Action complete)
         {
             if (maxCount <= 0)
             {
@@ -129,6 +130,7 @@ namespace Pixiview.Utils
             this.from = from;
             this.to = to;
             this.action = action;
+            this.complete = complete;
         }
 
         private void Start()
@@ -182,6 +184,7 @@ namespace Pixiview.Utils
                 });
             }
             App.DebugPrint($"parallel task done");
+            complete?.Invoke();
         }
 
         public void Dispose()
diff --git a/Pixiview/Utils/HttpUtility.cs b/Pixiview/Utils/HttpUtility.cs
index 7a181cb..066fa9b 100644
--- a/Pixiview/Utils/HttpUtility.cs
+++ b/Pixiview/Utils/HttpUtility.cs
@@ -17,7 +17,8 @@ namespace Pixiview.Utils
             HttpContent post = null,
             Action<HttpRequestHeaders> header = null,
             Func<T, string> namehandler = null,
-            Func<string, string> action = null)
+            Func<string, string> action = null,
+            Func<string, T> @return = null)
         {
             string content = null;
             if (post == null && !force && file != null && File.Exists(file))
@@ -89,6 +90,11 @@ namespace Pixiview.Utils
                         {
                             content = action(content);
                         }
+                        if (@return != null)
+                        {
+                            error = null;
+                            return @return(content);
+                        }
                     }
                     catch (Exception ex)
                     {
diff --git a/Pixiview/Utils/Stores.cs b/Pixiview/Utils/Stores.cs
index 3303879..a735062 100644
--- a/Pixiview/Utils/Stores.cs
+++ b/Pixiview/Utils/Stores.cs
@@ -5,6 +5,7 @@ using System.Linq;
 using System.Net;
 using System.Net.Http;
 using System.Text;
+using System.Text.RegularExpressions;
 using Newtonsoft.Json;
 using Pixiview.Illust;
 using Xamarin.Essentials;
@@ -478,6 +479,94 @@ namespace Pixiview.Utils
             return result;
         }
 
+        private static readonly Regex regexIllust = new Regex(
+            @"book_id\[\]"" value=""([0-9]+)"".*data-src=""([^""]+)"".*data-id=""([0-9]+)"".*" +
+            @"data-tags=""([^""]+)"".*data-user-id=""([0-9]+)"".*" +
+            @"class=""title"" title=""([^""]+)"".*data-user_name=""([^""]+)"".*" +
+            @"_bookmark-icon-inline""></i>([0-9]+)</a>",
+            RegexOptions.Compiled);
+
+        public static IllustItem[] LoadOnlineFavorites()
+        {
+            var list = new List<IllustItem>();
+            int p = 1;
+            while (p > 0)
+            {
+                var url = Configs.UrlFavoriteList;
+                if (p > 1)
+                {
+                    url += $"&p={p}";
+                }
+                var array = HttpUtility.LoadObject(
+                    null,
+                    url,
+                    null,
+                    out _,
+                    action: content =>
+                    {
+                        // page
+                        p = -1;
+                        var index = content.IndexOf("<span class=\"next\"><a href=\"?rest=show&amp;p=");
+                        if (index > 0)
+                        {
+                            var page = content.Substring(index + 45, content.IndexOf('\"', index + 45) - index - 45);
+                            if (int.TryParse(page, out var next))
+                            {
+                                p = next;
+                            }
+                        }
+                        // list
+                        index = content.IndexOf("<li class=\"image-item\">");
+                        if (index < 0)
+                        {
+                            return null;
+                        }
+                        content = content.Substring(index + 23);
+                        index = content.IndexOf("</li></ul><div class=\"clear\"></div>");
+                        if (index < 0)
+                        {
+                            return null;
+                        }
+                        return content.Substring(0, index);
+                    },
+                    @return: content =>
+                    {
+                        if (content == null)
+                        {
+                            return null;
+                        }
+                        var lines = content.Split("</li><li class=\"image-item\">");
+                        var illusts = new IllustItem[lines.Length];
+                        for (var i = 0; i < illusts.Length; i++)
+                        {
+                            var line = lines[i];
+                            var m = regexIllust.Match(line);
+                            if (m.Success)
+                            {
+                                illusts[i] = new IllustItem
+                                {
+                                    IsFavorite = true,
+                                    BookmarkId = m.Groups[1].Value,
+                                    ImageUrl = m.Groups[2].Value,
+                                    Id = m.Groups[3].Value,
+                                    Tags = m.Groups[4].Value.Split(' '),
+                                    UserId = m.Groups[5].Value,
+                                    Title = m.Groups[6].Value,
+                                    UserName = m.Groups[7].Value,
+                                    RatingCount = int.Parse(m.Groups[8].Value)
+                                };
+                            }
+                        }
+                        return illusts;
+                    });
+                if (array != null && array.Length > 0)
+                {
+                    list.AddRange(array);
+                }
+            }
+            return list.ToArray();
+        }
+
         public static ImageSource LoadIllustImage(string url)
         {
             return LoadImage(url, PersonalFolder, imageFolder);
@@ -561,6 +650,9 @@ namespace Pixiview.Utils
     {
         public bool Changed { get; private set; }
 
+        public FavoriteList() : base() { }
+        public FavoriteList(IEnumerable<IllustItem> collection) : base(collection) { }
+
         public new void Insert(int index, IllustItem item)
         {
             base.Insert(index, item);
@@ -603,7 +695,7 @@ namespace Pixiview.Utils
         public const string QueryDateKey = "query_date";
 
         public const int MaxPageThreads = 3;
-        public const int MaxThreads = 6;
+        public const int MaxThreads = 4;
         public const string Referer = "https://www.pixiv.net/";
         public const string RefererIllust = "https://www.pixiv.net/artworks/{0}";
         public const string RefererIllustRanking = "https://www.pixiv.net/ranking.php?{0}";
@@ -700,6 +792,7 @@ namespace Pixiview.Utils
         public static string UrlIllustUgoira => Prefix + "ajax/illust/{0}/ugoira_meta?lang=zh";
         public static string UrlIllustRecommendsInit => Prefix + "ajax/illust/{0}/recommend/init?limit=18&lang=zh";
         public static string UrlIllustRecommendsList => Prefix + "ajax/illust/recommend/illusts?{0}lang=zh";
+        public static string UrlFavoriteList => Prefix + "bookmark.php?rest=show";
 
         public static string BookmarkAdd => Prefix + "ajax/illusts/bookmarks/add";
         public static string BookmarkRpc => Prefix + "rpc/index.php";