Hold to new tab

Hold mouse button over link to open it in new tab. Requires to release mouse to take effect due to popup restrictions.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Hold to new tab
// @description Hold mouse button over link to open it in new tab. Requires to release mouse to take effect due to popup restrictions.
// @namespace   util
// @include     *
// @version     2016.2.15.18.05
// @grant       none
// @author      Jakub Mareda aka MXXIV
// @run-at      document-start
// ==/UserScript==

// Timeout after mousedown when the new tab timeout should be started
// this timeout is to prevent flickering when clicking links normally
var startCoundownTimeout = null;
var startCountdownTime = 100; //140;

// the actual countdown before displaying the link
var countdownTimeout = null;
var countdownTime = 500;

// Time when the mouse was pressed down
var mouseDownTime = -1;

// Target link
var link = null;
// progress bar object, initialized later
var progressBar;

function mouseDown(e) {
  // only left button
  if(e.button != 0)
    return;
    
  var el = e.target;
  //console.log("Mousedown");
  while(el!=null) {
    if(el.tagName && el.tagName.toLowerCase()=="a") {
      link = el;
      //console.log("... link!");
      startPreCountdown(e.clientX, e.clientY);
      return;
    }
    el = el.parentNode;
  }
  //console.log("... not a link.");
}
// Counts but doesn't display anything yet
function startPreCountdown(x,y) {
  mouseDownTime = performance.now();
  // add cancel listeners
  link.addEventListener("mouseout", failIfLeftButton);
  link.addEventListener("mouseup", failIfLeftButton);
  window.addEventListener("onunload", stopFail);
  // Don't use delay if delay is off
  if(startCountdownTime>0) {
    startCountdownTimeout = setTimeout(startCountdown, startCountdownTime, x, y);
  }
  else {
    startCountdown(x, y);
  }
}
// Starts displaying the loader
function startCountdown(x,y) {
  progressBar.x = x;
  progressBar.y = y;
  progressBar.start();

  countdownTimeout = setTimeout(stopExec, countdownTime);
}
// Stop and do the action
function stopExec() {
  //console.log("Open in new tab: "+link.href);
  if(link.target!="_blank") {
    if(link.target)
      link.setAttribute("oldtarget", link.target);
    link.target = "_blank";
    removeBlankOnClick(link);
  }
  //link.dispatchEvent(new MouseEvent("click"));
  stop();
}
// stop and do nothing
function stopFail() {
  //console.log("No action on "+link.href);
  stop();
}

function failIfLeftButton(mouseEvent) {
  if(mouseEvent.button == 0)
    stopFail();
}

function stop() {
  progressBar.stop();
  
  clearTimeout(countdownTimeout);
  countdownTimeout = null;
  
  clearTimeout(startCountdownTimeout);
  startCountdownTimeout = null;
  
  // clear listeners
  link.removeEventListener("mouseout", failIfLeftButton);
  link.removeEventListener("mouseup", failIfLeftButton);
  window.removeEventListener("onunload", stopFail);
}

function calculateTimePercentage() {
  var dt = performance.now()-mouseDownTime;
  return (dt/(startCountdownTime+countdownTime))*100;
}


function Renderer(callback) {
  this.percents = 0;
  this.x = 0;
  this.y = 0;
  this.getPercents = callback;
  this.colorFull = "rgba(86,125,255,0.8)";
  this.colorEmpty = "rgba(86,125,255,0.2)";
  
  this.rendering = false;
  // cached callback
  this.renderLoop = this.renderLoop.bind(this);
}
Renderer.prototype = {
  init: function() {
    var div = this.div;
    if(!div) {
      var div = this.div = document.createElement("div");
      div.style.position = "fixed";
      div.style.width = "40px";
      div.style.height = "6px";
      div.style.fontSize = "0px";
      div.innerHTML = "This div is used to render progress bar for New tab userscript.";
      div.style.display = "none";
      document.body.appendChild(div);
    }
    return div;
  },
  render: function() {
    var div = this.div?this.div:this.init();
    div.style.top = (this.y-8)+"px";
    // hardcoded half width
    div.style.left = (this.x-20)+"px";
    var c = this.colorFull,
        c2 = this.colorEmpty;
    var perc = Math.round(this.percents)+"";
    div.style.background = "linear-gradient(to right, "+c+" 0%,"+c+" "+perc+"%,"+c2+" "+perc+"%,"+c2+" 100%)";
  },
  renderLoop: function() {
    if(this.rendering) {
      if(this.getPercents)
        this.percents = this.getPercents();
      this.render();
      requestAnimationFrame(this.renderLoop);
    }
  },
  start: function() {
     this.rendering = true;
     (this.div ? this.div:this.init()).style.display = "block";
     this.renderLoop();
  },
  stop: function() {
     this.rendering = false;
     if(this.div)
       this.div.style.display = "none";
  }
}

progressBar = new Renderer(calculateTimePercentage);

window.addEventListener("mousedown", mouseDown);

function cancelEvent(e) {
  e.preventDefault();
  console.log("Cancel ", e.type);
  window.removeEventListener(e.type, cancelEvent);
  return false;
}
function cancelNext(evtName) {
  window.addEventListener(evtName, cancelEvent);
}
/** remove target="_blank" - or set it to original value **/
function removeBlank(e) {
  // it must happen AFTER the click event does what it should
  setTimeout((()=>{
    if(this.hasAttribute("oldtarget")) {
      this.setAttribute("target", this.getAttribute("oldtarget"));
      this.removeAttribute("oldtarget");
    }
    else {
      this.removeAttribute("target");
    }
  }).bind(this), 0);
  this.removeEventListener(e.type, removeBlank);
}
function removeBlankOnClick(link) {
  link.addEventListener("click", removeBlank);
}