bilibili 新动态顶栏简易修改

修改 bilibili 新动态顶栏, 使其更方便使用

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         bilibili 新动态顶栏简易修改
// @namespace    http://unown.moe
// @version      0.4.2
// @description  修改 bilibili 新动态顶栏, 使其更方便使用
// @author       Unown Hearn
// @license      MIT License
// @match        *://t.bilibili.com/*
// @grant        none
// ==/UserScript==

/*
    history:
        [2017.12.1] 0.0 简单地让 bilibili 网页端新版动态的顶栏固定,
   并隐藏原本冒出来的新顶栏

        [2017.12.1] 0.1 让顶栏在小窗口时也完全显示, 顶栏的左部分可以自动折叠

        [2017.12.1] 0.2 顶栏的搜索栏在聚焦时会伸长到 2.5 倍

        [2017.12.2] 0.3 右边栏也可以自动折叠了, 顶栏左部分的主站项不会消失

        [2017.12.2] 0.3.1 完善文档

        [2017.12.2] 0.3.2 偷了个懒, 让收进去的项横着显示, 规避了错位问题,
   另外解决了一个初始化时变成 2 行的 bug

        [2017.12.2] 0.3.3 修复了一行可能会变成两行的 bug
   (除了尺寸非常小的情况之外)

        [2017.12.2] 0.4 暴力解决了网络不好时需要等待网页加载而无法执行脚本的问题

        [2017.12.2] 0.4.1 修复 bug, 改进浏览器支持(chrome, safari)

        [2017.12.2] 0.4.1 正式发布于
   greasyfork(https://greasyfork.org/zh-CN/scripts/35912-bilibili-%E6%96%B0%E5%8A%A8%E6%80%81%E9%A1%B6%E6%A0%8F%E7%AE%80%E6%98%93%E4%BF%AE%E6%94%B9)

        [2017.12.2] 0.4.2 添加遗漏掉的协议

    TODO:
        * 点击省略号后, 使面板保持开启, 直到点击其他位置或再点一次省略号

        * 要不要解决宽度过小时会发生的问题? 那并不是应该算是正常使用...

*/

(function() {
//'use strict';
//< 不然 safari 会报错

// 此脚本只管动态首页
if (!/^(\/|\/\?.*)$/.test(window.location.pathname)) {
  return;
}

console.log('启用了 userscript \'固定 bilibili 新动态的顶栏 1.0\'');

/**
 * 本 userscript 是否成功初始化
 *
 * @type boolean
 */
var initialized = false;

/**
 * 顶栏
 *
 * @type {HTMLElement}
 */
var bili_wrapper;
/**
 * 顶栏的左导航栏
 *
 * @type {HTMLElement}
 */
var nav_con_fl;
/**
 * 顶栏的右导航栏 (不包含右导航栏)
 *
 * @type {HTMLElement}
 */
var nav_con_fr;
/**
 * 顶栏的投稿栏
 *
 * @type {HTMLElement}
 */
var nav_upload;
/**
 * 顶栏的搜索栏 (从右导航栏分离)
 *
 * @type {HTMLElement}
 */
var nav_search;

/**
 * 是否在使用备选方案. 换句话说, 是否顶栏右部分被挤压了
 *
 * @type boolean
 */
var in_plan_b = false;

/**
 * 获取窗口宽度
 *
 * @returns {number} 窗口宽度
 */
function getWindowWidth() {
  return $(window).width();
}

/**
 * 获取元素的真实大小
 *
 * @param {HTMLElement} elem
 */
function getRealWidth(elem) {
  var width;

  if (elem.style.display === 'none') {
    var old_position = elem.style.position;
    var old_visibility = elem.style.visibility;
    elem.style.position = 'absolute';
    elem.style.visibility = 'hidden';
    elem.style.display = 'inline';
    width = elem.offsetWidth;
    elem.style.position = old_position;
    elem.style.display = 'none';
    elem.style.visibility = old_visibility;
    return width;
  }
  return elem.offsetWidth;
}

/**
 *  调整顶栏的某一部分导航栏
 *
 *  @param {HTMLElement} nav 导航栏
 *  @param {number} max_width 外部认为容许的最大宽度
 *  @param {HashMap} [options] 更多的选项, 包括:
 *  @param {HTMLElement} [options.plan_b] 备选可调整的导航栏
 *  @param {number} [options.nav_search_width_for_plan_b] 搜索框的最终长度, 在计算备选方案的长度时要用到
 *  @param {boolean} [options.is_left] 如果为真, 则代表正在调整的是左部分
 *
 *  @returns {number} 隐藏项的数量
 */
function __adjustNav(
    nav, max_width, options /*plan_b, nav_search_width_for_plan_b*/) {
  //< plan_b 为 undefined 代表只要管好自己就行了
  // console.log(["max width:", max_width]);
  options = options || {};

  // console.log(nav, nav.querySelector(".ul"));
  // querySelectorAll 蠢爆了, 不能限定到仅自己的子元素...
  // 包含外部项的 ul
  var nav_ul = nav.querySelector('ul');
  // "更多"的面板
  var more_panel = nav_ul.querySelector('.more-panel');
  // 外部项
  var outside_items = Array.prototype.slice.call(nav_ul.children);
  // 包含"更多"按钮的项, 位于外部项的最末尾. 可能会不 display, 但位置依旧不变
  var more_button_item = outside_items[outside_items.length - 1];



  var min_width = getRealWidth(more_button_item);
  if (options.is_left) {
    min_width += outside_items[0].offsetWidth;
    // 外部项的数组要忽略掉"第一项"和"更多"按钮
    outside_items = outside_items.slice(1, outside_items.length - 1);
  } else {
    // 外部项的数组要忽略掉"更多"按钮
    outside_items = outside_items.slice(0, outside_items.length - 1);
  }

  if (options.plan_b) {  // 有备用计划
    var max_width_for_plan_b = getWindowWidth() - min_width -
        nav_upload.offsetWidth - options.nav_search_width_for_plan_b;
    if (in_plan_b) {  //< 代表目前要先调整右部分
      if (__adjustNav(options.plan_b, max_width_for_plan_b) ===
          0) {  //< 右边没有隐藏项了, 代表可以试着调整左边了
        in_plan_b = false;

        var max_width = getWindowWidth() - nav_con_fr.offsetWidth -
            nav_upload.offsetWidth - options.nav_search_width_for_plan_b;
        __adjustNav(nav, max_width, {is_left: true});
        return;
      } else {  // 右边还没调整完, 不能调整左边, 所以直接返回
        return;
      }
    } else if (max_width < min_width) {  //< 左边不能再压榨空间了
      if (nav.offsetWidth > min_width) {
        __adjustNav(nav, min_width, {is_left: true});  //< 先把左边调整好
        // console.log([min_width, nav.offsetWidth]);
      }
      in_plan_b = true;
      console.log([nav_con_fr.offsetWidth, max_width_for_plan_b]);
      __adjustNav(options.plan_b, max_width_for_plan_b);
      return;
    }
  }

  nav.style.maxWidth = (max_width).toString() + 'px';
  // 包含隐藏项的 ul
  var more_panel_ul = more_panel.querySelector('ul');
  // 隐藏项
  var hidden_items = Array.prototype.slice.call((more_panel_ul.children));

  // 外部项的总长度
  var sum_width = 0;

  // 第一个隐藏的项(或 undefined), 用于插入新的隐藏项
  let first_hidden_item = hidden_items[0] || undefined;

  // 记录隐藏项的数量, 会在判定是否计算"更多"按钮时使用
  var hidden_count = hidden_items.length;

  // 如果为真, 则代表外边已经容不下更多项了
  var overflowed = false;

  // 将会溢出的项放入"更多"面板中
  for (var i = 0; i < outside_items.length; i++) {
    if (!overflowed) {
      sum_width += outside_items[i].offsetWidth;
      var actual_width =
          ((i === outside_items.length - 1) && hidden_count === 0) ?
          sum_width + min_width - getRealWidth(more_button_item) :
          sum_width + min_width;
      // console.log([sum_width, actual_width, getRealWidth(more_button_item),
      // more_button_item]);
      if (actual_width > max_width) {
        overflowed = true;
      }
    }

    if (overflowed) {
      // console.log(["hide:", i, outside_items[i]]);
      hidden_count++;
      if (first_hidden_item) {
        // console.log(["insert before", outside_items[i], first_hidden_item]);
        more_panel_ul.insertBefore(outside_items[i], first_hidden_item);
      } else {
        // console.log(["append child", outside_items[i]]);
        more_panel_ul.appendChild(outside_items[i]);
      }
    }
  }

  // 如果没有溢出, 试着将"更多"面板中的项取回顶栏的此半部分, 直到会溢出
  if (!overflowed) {
    for (var i = 0; i < hidden_items.length; i++) {
      sum_width += hidden_items[i].offsetWidth;

      var actual_width = (i === hidden_items.length - 1) ?
          sum_width + min_width - getRealWidth(more_button_item) :
          sum_width + min_width;
      if (actual_width > max_width) {
        overflowed = true;
        break;
      }

      hidden_count--;
      nav_ul.insertBefore(hidden_items[i], more_button_item);
    }
  }

  if (overflowed) {
    more_button_item.style.display = 'list-item';
  } else {
    more_button_item.style.display = 'none';
  }

  return hidden_count;
}

/**
 * 是否正在调整顶栏的导航栏
 *
 * @type boolean
 */
var in_adjusting = false;

/**
 * 调整顶栏
 *
 * @param {number} window_width 窗口的大小
 * @param {number} [nav_search_width] 搜索栏的最终大小
 *
 * @returns {void}
 */
function adjustNav(window_width, nav_search_width) {
  if (in_adjusting) {
    return;
  }
  in_adjusting = true;

  if (!nav_search_width) {
    nav_search_width = nav_search.offsetWidth;
  }

  var max_width = window_width - nav_con_fr.offsetWidth -
      nav_upload.offsetWidth - nav_search_width;

  __adjustNav(nav_con_fl, max_width, {
    plan_b: nav_con_fr,
    nav_search_width_for_plan_b: nav_search_width,
    is_left: true
  });

  in_adjusting = false;
}

/**
 *
 * @param {*} selector 选择器
 * @param {*} inv 尝试间隔
 * @param {*} callback 回调函数, 以毫秒为单位
 *
 * @returns {void}
 */
function waitUntilLoaded(selectors, inv, callback) {
  for (var i = 0; i < selectors.length; i++) {
    if (!document.querySelector(selectors[i])) {
      setTimeout(function() {
        waitUntilLoaded(selectors, inv, callback);
      }, inv);
      return;
    }
  }
  callback();
}

// 在页面加载后初始化本 userscript
function initialize() {
  // 让 home-container 能在正确的位置显示
  {
    var home_container = document.querySelector('.home-container');
    if (home_container) {
      home_container.style = 'position: absolute; top:42px;';
    } else {
      console.log('home-container 不存在!');
    }
  }

  // 使顶栏固定
  {
    var header = document.querySelector('.bili-header-m');
    if (header) {
      header.style = 'position: fixed; top:0; width: 100%;';
    } else {
      console.log('bili-header-m 不存在!');
      return;
    }
  }

  // ~~让顶栏的内容能正常显示~~
  {
    bili_wrapper = document.querySelector('.bili-wrapper');
    nav_con_fl = bili_wrapper.querySelector('.nav-con.fl');
    nav_con_fr = bili_wrapper.querySelector('.nav-con.fr');
    nav_upload = bili_wrapper.querySelector('.up-load');

    if (!bili_wrapper || !nav_con_fl || !nav_con_fr || !nav_upload) {
      console.log(
          '.bili-wrapper 或 .nav-con.fl 或 .nav-con.fr 或 .up-load.fr 不存在!');
      console.log([bili_wrapper, nav_con_fl, nav_con_fr, nav_upload]);
      return;
    } else {
      // nav_con_fl.style = "font-size: 0.8em; text-indent: -5px;";
      // nav_con_fr.style = "float: left;";
      // nav_upload.style = "float: left;";
      // bili_wrapper.appendChild(nav_upload);
      // nav_con_fl.style = "overflow: hidden;";
      // nav_con_fl_ul = nav_con_fl.querySelector("ul");
    }
  }

  // 把搜索栏挪出来
  {
    nav_search = bili_wrapper.querySelector('.nav-search');
    if (nav_search) {
      bili_wrapper.appendChild(nav_search);
    } else {
      console.log('.nav-search 不存在');
    }
    nav_search.style.marginRight = '0';
  }

  // 添加"更多"的按钮
  function addButtonMore(nav, is_right) {
    var ul = nav.querySelector('ul');

    var item_more = document.createElement('li');
    item_more.classList.add('nav-item');
    item_more.classList.add('more');
    item_more.style.paddingLeft = '11px';
    item_more.style.paddingRight = '11px';
    if (is_right) {
      item_more.style.display = 'none';
    }


    var button_area = document.createElement('div');
    button_area.classList.add('button-area');
    button_area.classList.add('c-pointer');  // 照猫画虎

    var more_button = document.createElement('div');
    more_button.classList.add('icon-font');
    more_button.classList.add('icon-more-1');

    var more_panel = document.createElement('div');
    more_panel.classList.add('more-panel');
    // more_panel.style = "position: absolute; display: none; left:";
    more_panel.style = 'position: absolute;';

    var more_panel_ul = document.createElement('ul');

    button_area.appendChild(more_button);
    item_more.appendChild(button_area);
    ul.appendChild(item_more);
    more_panel.appendChild(more_panel_ul);
    button_area.appendChild(more_panel);
  }

  addButtonMore(nav_con_fl);
  addButtonMore(nav_con_fr, true);

  // 让搜索框可以伸缩
  {
    var search_input = document.querySelector('.nav-search input');
    search_input.addEventListener('focusin', function() {
      // console.log(["focusin"]);
      adjustNav(window_width, 142);  // workaround
      search_input.style.width = '100px';
    });
    search_input.addEventListener('focusout', function() {
      search_input.style.width = '40px';
      setTimeout(function() {
        adjustNav(window_width);
      }, 250);  // workaround
    });
  }

  // css 相关
  {
    var sheet = document.createElement('style');

    sheet.innerHTML =
        // 抄自网站自己的 css, 用来显示面板
        '.nav-con .more-panel {' +
        'position: absolute;' +
        //'width: 94px;' +
        'text-align: center;' +
        'top: 45px;' +
        'left: -4px;' +
        //"right: 5px;"+
        'background: #fff;' +
        'border: 1px solid #e5e9ef;' +
        '-webkit-box-shadow: 0 11px 12px 0 rgba(106,115,133,0.12);' +
        'box-shadow: 0 11px 12px 0 rgba(106,115,133,0.12);' +
        'border-radius: 8px;' +
        'color: #222;' +
        'z-index: 10;' +
        // 适应大小
        'width: -moz-max-content;' +     // firefox
        'width: max-content;' +          // chrome
        'width: -webkit-max-content;' +  // safari
        '}\n' +
        // 右边
        '.nav-con.fr .more-panel {' +
        'right: 4px;' +
        'left: auto;' +
        '}\n' +
        // 抄自网站自己的 css, 用来显示面板的角
        '.nav-con .more-panel:after {' +
        'content: "";' +
        'display: block;' +
        'border-top: 1px solid #e5e9ef;' +
        'border-left: 1px solid #e5e9ef;' +
        '-webkit-transform: rotate(45deg);' +
        'transform: rotate(45deg);' +
        'width: 8px;' +
        'height: 8px;' +
        'position: absolute;' +
        'top: -5px;' +
        'left: 12px;' +
        'background: #fff;' +
        '}\n' +
        // 右边
        '.nav-con.fr .more-panel:after {' +
        'right: 12px;' +
        'left: auto;' +
        '}\n' +
        // ~~让更多面板上的项居中~~ 与面板中的项有关的样式
        '.more-panel .nav-item {' +
        //'float: none!important;' +
        'transition: none!important;' +
        //"clear: both;" +
        '}\n' +
        // 自适应宽度
        '@media screen and (max-width: 980px) {' +
        '.bili-header-m .bili-wrapper {' +
        'width: 100%;' +
        '}' +
        '}\n' +
        // 悬浮显示面板
        '.nav-item.more .more-panel {' +
        'visibility: hidden;' +
        'transition-duration: 0.1s;' +
        'transition-delay: 0.1s;' +
        '}\n' +
        '.nav-item.more:hover .more-panel {' +
        'visibility: visible;' +
        'transition-duration: 0.1s;' +
        'transition-delay: 0s;' +
        '}\n' +
        // 让 home 的位置好看些
        '.nav-item.home { ' +
        'margin-left: 0!important;' +
        'transition-duration: 0s;' +
        'transition-delay: 0s;' +
        '}\n';


    document.head.appendChild(sheet);
  }

  // setTimeout(function() {
  adjustNav(getWindowWidth());
  initialized = true;
  //}, 1000);



  // 隐藏会冒出来的新顶栏
  {
    var sticky_bar = document.querySelector('.sticky-bar');
    if (sticky_bar) {
      sticky_bar.style = 'visibility: hidden;';
    } else {
      console.log('sticky-bar 不存在!');
    }
  }
}

waitUntilLoaded(['.nav-con.fl', '.nav-con.fr', '.up-load.fr'], 50, initialize);

/**
 * 用于检测是否是窗口的宽度发生了变化
 *
 * @type {number}
 */
var window_width = getWindowWidth();

// 监视窗口调整大小
window.addEventListener('resize', function() {

  if (!initialized) {
    return;
  }

  var new_width = getWindowWidth();
  if (window_width === new_width) {
    return;
  }

  window_width = new_width;

  // nav_con_fl, nav_con_fr, nav_upload;

  adjustNav(window_width);

  // console.log(nav_con_fl.style.maxWidth);

});

})();