使用书签创建弹出菜单

Creating a popup menu with bookmarklets

这是我的第一个问题,所以请指点我如何提出更好的问题。 无论如何,我将如何制作一个显示链接的可切换菜单,该菜单使用书签激活。我试图找到答案,但都没有结果。我需要为此创建一个新元素吗?

您将需要使用 vanilla JS 创建弹出菜单。我还实现了拖动功能。唯一需要做的就是在滚动页面时正确设置位置。

DOM布局

需要以下最重要的元素和样式。

<div style="position:absolute; z-index:2147483647">
  <div style="position: relative">
    <div style="position:relative; display:inline-block; left:0">Bookmarklet Links</div>
    <div style="position:relative; float:right">×</div>
  </div>
  <div>
    <p>Click the links to open a new tab!</p>
    <ul>
      <li>
        <a href="https://www.google.com" target="_blank">Google</a>
      </li>
      <li>
        <a href="https://www.bing.com" target="_blank">Bing</a>
      </li>
      <li>
        <a href="https://duckduckgo.com" target="_blank">DuckDuckGO</a>
      </li>
    </ul>
  </div>
</div>

您可以保存以下小书签:

javascript:!function(){var c=0x1f4,d=0x12c,e='#AAA',f=0x1,g=0x20,h='#444',i='#FFF',j='Bookmarklet\x20Links',k=~~(document['documentElement']['clientWidth']/0x2-c/0x2),l=~~(document['documentElement']['clientHeight']/0x2-d/0x2),m=~~(0.8*g),n=document['createElement']('DIV');Object['assign'](n['style'],{'position':'absolute','left':k+'px','top':l+'px','zIndex':Number['MAX_SAFE_INTEGER'],'width':c+'px','height':d+'px','background':e,'border':f+'px\x20solid\x20black'});var o=document['createElement']('DIV');Object['assign'](o['style'],{'position':'relative','width':c+'px','height':g+'px','background':h,'borderBottom':f+'px\x20solid\x20black'});var p=document['createElement']('DIV');Object['assign'](p['style'],{'position':'relative','display':'inline-block','left':0x0,'width':~~(c-0x2*m)+'px','lineHeight':g+'px','color':i,'fontSize':~~(0.667*g)+'px','marginLeft':~~(m/0x3)+'px'}),p['textContent']=j;var q=document['createElement']('DIV'),r=~~((g-m)/0x2);Object['assign'](q['style'],{'position':'relative','float':'right','right':r+'px','top':r+'px','width':m+'px','height':m+'px','background':'#F00','border':f+'px\x20solid\x20black','color':'#FFF','lineHeight':m+'px','textAlign':'center','fontSize':m+'px','marginLeft':'auto','marginRight':0x0});var s=document['createElement']('DIV');Object['assign'](s['style'],{'padding':'1em'});var t=document['createElement']('P');t['textContent']='Click\x20the\x20links\x20to\x20open\x20a\x20new\x20tab!',s['appendChild'](t);var u=document['createElement']('UL');[{'name':'Google','url':'https://www.google.com'},{'name':'Bing','url':'https://www.bing.com'},{'name':'DuckDuckGO','url':'https://duckduckgo.com'}]['forEach'](c=>{var d=document['createElement']('LI'),e=document['createElement']('A');e['setAttribute']('href',c['url']),e['setAttribute']('target','_blank'),e['textContent']=c['name'],d['appendChild'](e),u['appendChild'](d);}),s['appendChild'](u),q['addEventListener']('click',function c(d){q['removeEventListener']('click',c,!0x1);o['removeChild'](q);n['removeChild'](o);n['removeChild'](s);document['body']['removeChild'](n);},!0x1),q['textContent']='×',o['appendChild'](p),o['appendChild'](q),n['appendChild'](o),n['appendChild'](s),document['body']['appendChild'](n),function(c){var d=function(c){var d=c['getBoundingClientRect'](),e=window['pageXOffset']||document['documentElement']['scrollLeft'],f=window['pageYOffset']||document['documentElement']['scrollTop'];return{'top':d['top']+f,'left':d['left']+e};}(c['parentElement']),e=!0x1,f={'x':0x0,'y':0x0},g={'x':d['left'],'y':d['top']};c['parentElement']['addEventListener']('mousedown',function(d){e=!0x0,f['x']=d['clientX'],f['y']=d['clientY'],c['parentElement']['style']['cursor']='move';}),c['parentElement']['addEventListener']('mouseup',function(d){e=!0x1,g['x']=parseInt(c['parentElement']['style']['left'])||0x0,g['y']=parseInt(c['parentElement']['style']['top'])||0x0,c['parentElement']['style']['cursor']='auto';}),document['addEventListener']('mousemove',function(d){if(!e)return;var h={'x':d['clientX']-f['x'],'y':d['clientY']-f['y']},i={'x':g['x']+h['x'],'y':g['y']+h['y']};i['x']<0x0?i['x']=0x0:i['x']+c['parentElement']['offsetWidth']>document['documentElement']['clientWidth']&&(i['x']=document['documentElement']['clientWidth']-c['parentElement']['offsetWidth']);i['y']<0x0?i['y']=0x0:i['y']+c['parentElement']['offsetHeight']>document['documentElement']['clientHeight']&&(i['y']=document['documentElement']['clientHeight']-c['parentElement']['offsetHeight']);c['parentElement']['style']['left']=i['x']+'px',c['parentElement']['style']['top']=i['y']+'px';});}(o);}(window);

缩小和混淆的方法

对于混淆器,我将 "Identifier Names Generator" 设置为 "mangled" 并检查了 "Rename Globals"。

重要提示: 取消选择 "String Array" 否则您无法添加新的 link 条目。

注意事项

脚本不允许超出初始宽度和高度。可以合并 this post 中的以下宽度和高度方法来修复此限制。


源代码

(function(window) {
  var links = [{
    name: 'Google',
    url: 'https://www.google.com'
  }, {
    name: 'Bing',
    url: 'https://www.bing.com'
  }, {
    name: 'DuckDuckGO',
    url: 'https://duckduckgo.com'
  }];
  var props = {
    width: 500,
    height: 300,
    background: '#AAA',
    borderThickness: 1,
    headerHeight: 32,
    headerBackground: '#444',
    headerTitleColor: '#FFF',
    windowTitle: 'Bookmarklet Links'
  };
  var windowPosition = {
    left: ~~((document.documentElement.clientWidth / 2) - (props.width / 2)),
    top: ~~((document.documentElement.clientHeight / 2) - (props.height / 2)),
  }
  var btnSize = ~~(props.headerHeight * 0.8);
  var popupEl = document.createElement('DIV');
  Object.assign(popupEl.style, {
    position: 'absolute',
    left: windowPosition.left + 'px',
    top: windowPosition.top + 'px',
    zIndex: Number.MAX_SAFE_INTEGER,
    width: props.width + 'px',
    height: props.height + 'px',
    background: props.background,
    border: props.borderThickness + 'px solid black'
  });
  var popupHeader = document.createElement('DIV');
  Object.assign(popupHeader.style, {
    position: 'relative',
    width: (props.width) + 'px',
    height: props.headerHeight + 'px',
    background: props.headerBackground,
    borderBottom: props.borderThickness + 'px solid black'
  });
  var popupHeaderTitle = document.createElement('DIV');
  Object.assign(popupHeaderTitle.style, {
    position: 'relative',
    display: 'inline-block',
    left: 0,
    width: ~~(props.width - btnSize * 2) + 'px',
    lineHeight: props.headerHeight + 'px',
    color: props.headerTitleColor,
    fontSize: ~~(props.headerHeight * 0.667) + 'px',
    marginLeft: ~~(btnSize / 3) + 'px'
  });
  popupHeaderTitle.textContent = props.windowTitle;
  var closeButton = document.createElement('DIV');
  var margin = ~~((props.headerHeight - btnSize) / 2);
  Object.assign(closeButton.style, {
    position: 'relative',
    float: 'right',
    right: margin + 'px',
    top: margin + 'px',
    width: btnSize + 'px',
    height: btnSize + 'px',
    background: '#F00',
    border: props.borderThickness + 'px solid black',
    color: '#FFF',
    lineHeight: btnSize + 'px',
    textAlign: 'center',
    fontSize: btnSize + 'px',
    marginLeft: 'auto',
    marginRight: 0
  });
  var popupBody = document.createElement('DIV');
  Object.assign(popupBody.style, {
    padding: '1em'
  });
  var p = document.createElement('P');
  p.textContent = 'Click the links to open a new tab!';
  popupBody.appendChild(p);
  var listEl = document.createElement('UL');
  links.forEach(link => {
    var itemEl = document.createElement('LI');
    var anchorEl = document.createElement('A');
    anchorEl.setAttribute('href', link.url);
    anchorEl.setAttribute('target', '_blank');
    anchorEl.textContent = link.name;
    itemEl.appendChild(anchorEl);
    listEl.appendChild(itemEl);
  });
  popupBody.appendChild(listEl);
  closeButton.addEventListener('click', destroyWindow, false);
  closeButton.textContent = '×';
  popupHeader.appendChild(popupHeaderTitle);
  popupHeader.appendChild(closeButton);
  popupEl.appendChild(popupHeader);
  popupEl.appendChild(popupBody);
  document.body.appendChild(popupEl);
  draggable(popupHeader);
  function destroyWindow(e) {
    closeButton.removeEventListener('click', destroyWindow, false);
    popupHeader.removeChild(closeButton);
    popupEl.removeChild(popupHeader);
    popupEl.removeChild(popupBody);
    document.body.removeChild(popupEl);
  }
  /* Source: https://plainjs.com/javascript/styles/get-the-position-of-an-element-relative-to-the-document-24/ */
  function offset(el) {
    var rect = el.getBoundingClientRect(),
    scrollLeft = window.pageXOffset || document.documentElement.scrollLeft,
    scrollTop = window.pageYOffset || document.documentElement.scrollTop;
    return { top: rect.top + scrollTop, left: rect.left + scrollLeft }
  }
  /* Source: https://gist.github.com/remarkablemark/5002d27442600510d454a5aeba370579 */
  function draggable(el) {
    var initialOffset = offset(el.parentElement);
    var isMouseDown = false;
    var currPos = { x : 0, y : 0 };
    var elPos = { x : initialOffset.left, y : initialOffset.top };
    el.parentElement.addEventListener('mousedown', onMouseDown);
    function onMouseDown(event) {
      isMouseDown = true;
      currPos.x = event.clientX;
      currPos.y = event.clientY;
      el.parentElement.style.cursor = 'move';
    }
    el.parentElement.addEventListener('mouseup', onMouseUp);
    function onMouseUp(event) {
      isMouseDown = false;
      elPos.x = parseInt(el.parentElement.style.left) || 0;
      elPos.y = parseInt(el.parentElement.style.top) || 0;
      el.parentElement.style.cursor = 'auto';
    }
    document.addEventListener('mousemove', onMouseMove);
    function onMouseMove(event) {
      if (!isMouseDown) return;
      var delta = { x : event.clientX - currPos.x, y: event.clientY - currPos.y };
      var pos = { x : elPos.x + delta.x, y : elPos.y + delta.y };
      if (pos.x < 0) {
        pos.x = 0;
      } else if (pos.x + el.parentElement.offsetWidth > document.documentElement.clientWidth) {
        pos.x = document.documentElement.clientWidth - el.parentElement.offsetWidth;
      }
      if (pos.y < 0) {
        pos.y = 0;
      } else if (pos.y + el.parentElement.offsetHeight > document.documentElement.clientHeight) {
        pos.y = document.documentElement.clientHeight - el.parentElement.offsetHeight;
      }
      el.parentElement.style.left = pos.x + 'px';
      el.parentElement.style.top = pos.y + 'px';
    }
  }
})(window);

改善

您会注意到,如果您的光标在拖动时离开屏幕(并且您松开按钮),window 将卡在拖动中。您可以在全球范围内检测到这一点,但您还需要弄清楚如何将位置重新初始化为最后已知的 "good" 位置。

document.addEventListener('mouseup', onGlobalMouseUp);
function onGlobalMouseUp(event) {
  if (
    (event.clientX < 0 || event.clientX > document.documentElement.clientWidth) ||
    (event.clientY < 0 || event.clientY > document.documentElement.clientHeight)
  ) {
    if (isMouseDown) {
      isMouseDown = false; // Draggged off-screen
      popupEl.style.cursor = 'auto';
    }
  }
}

最后,不要向小书签按钮发送垃圾邮件,因为它会创建相同 window 的多个实例。可以添加代码以在创建新代码之前检测 window 的存在。关闭它可以隐藏它,所以它只会使现有的再次可见。多个 windows 将打破关闭监听器。