Greasy Fork is available in English.
原生js右键弹出菜单
Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta
// @require https://update.greasyfork.org/scripts/464425/1178359/MyContextMenu.js
// ==UserScript==
// @name MyContextMenu
// @description http://https://wish123.cnblogs.com/?MyContextMenu
// @version 1.0
// @description 原生js右键弹出菜单
// @author Wilson
// @license MIT
// modify by https://github.com/electerious/basicContext/
(function() {
if(document.querySelector("#myContextMenuStyle")) {
return;
}
let style = `
<style id="myModalStyle">
/* base css */
.basicContext,
.basicContext * {
box-sizing: border-box;
}
.basicContextContainer {
position: fixed;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1000;
-webkit-tap-highlight-color: transparent;
}
.basicContext {
position: absolute;
opacity: 0;
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
}
.basicContext__item {
cursor: pointer;
}
.basicContext__item--separator {
float: left;
width: 100%;
height: 1px;
cursor: default;
}
.basicContext__item--disabled {
cursor: default;
}
.basicContext__data {
min-width: 140px;
padding-right: 20px;
text-align: left;
white-space: nowrap;
}
.basicContext__icon {
display: inline-block;
}
.basicContext--scrollable {
height: 100%;
-webkit-overflow-scrolling: touch;
overflow-y: auto;
}
.basicContext--scrollable .basicContext__data {
min-width: 160px;
}
/* default theme css */
.basicContext {
padding: 6px;
background-color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.4), 0 0 1px rgba(0, 0, 0, 0.2);
border-radius: 3px;
}
.basicContext__item {
margin-bottom: 2px;
}
.basicContext__item--separator {
margin: 4px 0;
background-color: rgba(0, 0, 0, 0.1);
}
.basicContext__item--disabled {
opacity: 0.5;
}
.basicContext__item:last-child {
margin-bottom: 0;
}
.basicContext__data {
padding: 6px 8px;
color: #333;
border-radius: 2px;
}
.basicContext__item:not(.basicContext__item--disabled):hover
.basicContext__data {
color: #fff;
background-color: #4393e6;
}
.basicContext__item:not(.basicContext__item--disabled):active
.basicContext__data {
background-color: #1d79d9;
}
.basicContext__icon {
margin-right: 10px;
width: 12px;
text-align: center;
}
/* [自定义] 自定义css样式 */
.basicContextContainer{
width: auto;
height: auto;
}
.basicContext {
padding: 0px;
min-width: 150px;
height: auto;
background-color: rgba(241, 240, 240, 0.96);
}
.basicContext table{
padding: 4px 0;
width: 100%;
}
.basicContext__item {
margin-bottom: 0px;
}
.basicContext__data {
padding: 2px 12px;
border-radius:0px;
font-size: 13.8px;
color: #3a383a;
}
.basicContext__item--separator {
margin: 2px 0;
}
.basicContext__item:not(.basicContext__item--disabled):hover .basicContext__data {
background-color: #4d92f8;
}
</style>
`
document.body.insertAdjacentHTML("beforeend", style);
})();
"use strict";
!(function (basicContext, callback) {
"undefined" != typeof module && module.exports
? (module.exports = callback())
: "function" == typeof define && define.amd
? define(callback)
: (window[basicContext] = callback());
})("basicContext", function () {
let overflow = null
const ITEM = 'item',
SEPARATOR = 'separator'
const dom = function(elem = '') {
return document.querySelector('.basicContext ' + elem)
}
const valid = function(item = {}) {
let emptyItem = (Object.keys(item).length===0 ? true : false)
if (emptyItem===true) item.type = SEPARATOR
if (item.type==null) item.type = ITEM
if (item.class==null) item.class = ''
if (item.visible!==false) item.visible = true
if (item.icon==null) item.icon = null
if (item.title==null) item.title = 'Undefined'
// Add disabled class when item disabled
if (item.disabled!==true) item.disabled = false
if (item.disabled===true) item.class += ' basicContext__item--disabled'
// Item requires a function when
// it's not a separator and not disabled
if (item.fn==null && item.type!==SEPARATOR && item.disabled===false) {
console.warn(`Missing fn for item '${ item.title }'`)
return false
}
return true
}
const buildItem = function(item, num) {
let html = '',
span = ''
// Parse and validate item
if (valid(item)===false) return ''
// Skip when invisible
if (item.visible===false) return ''
// Give item a unique number
item.num = num
// Generate span/icon-element
if (item.icon!==null) span = `<span class='basicContext__icon ${ item.icon }'></span>`
// [自定义]
item.extAttr = item.extAttr || ""
// Generate item
if (item.type===ITEM) {
html = `
<tr class='basicContext__item ${ item.class }'>
<td class='basicContext__data' data-num='${ item.num }' ${item.extAttr}>${ span }${ item.title }</td>
</tr>
`
} else if (item.type===SEPARATOR) {
html = `
<tr class='basicContext__item basicContext__item--separator'></tr>
`
}
return html
}
const build = function(items) {
let html = ''
html += `
<div class='basicContextContainer'>
<div class='basicContext'>
<table cellspacing="0">
<tbody>
`
items.forEach((item, i) => html += buildItem(item, i))
html += `
</tbody>
</table>
</div>
</div>
`
return html
}
const getNormalizedEvent = function(e = {}) {
let pos = {
x : e.clientX,
y : e.clientY
}
if (e.type==='touchend' && (pos.x==null || pos.y==null)) {
// We need to capture clientX and clientY from original event
// when the event 'touchend' does not return the touch position
let touches = e.changedTouches
if (touches!=null&&touches.length>0) {
pos.x = touches[0].clientX
pos.y = touches[0].clientY
}
}
// Position unknown
if (pos.x==null || pos.x < 0) pos.x = 0
if (pos.y==null || pos.y < 0) pos.y = 0
return pos
}
const getPosition = function(e, context) {
// Get the click position
let normalizedEvent = getNormalizedEvent(e)
// Set the initial position
let x = normalizedEvent.x,
y = normalizedEvent.y
// Get size of browser
let browserSize = {
width : window.innerWidth,
height : window.innerHeight
}
// Get size of context
let contextSize = {
width : context.offsetWidth,
height : context.offsetHeight
}
// Fix position based on context and browser size
if ((x + contextSize.width) > browserSize.width) x = x - ((x + contextSize.width) - browserSize.width)
if ((y + contextSize.height) > browserSize.height) y = y - ((y + contextSize.height) - browserSize.height)
// Make context scrollable and start at the top of the browser
// when context is higher than the browser
if (contextSize.height > browserSize.height) {
y = 0
context.classList.add('basicContext--scrollable')
}
// Calculate the relative position of the mouse to the context
let rx = normalizedEvent.x - x,
ry = normalizedEvent.y - y
return { x, y, rx, ry }
}
const bind = function(item = {}) {
if (item.fn==null) return false
if (item.visible===false) return false
if (item.disabled===true) return false
dom(`td[data-num='${ item.num }']`).onclick = item.fn
dom(`td[data-num='${ item.num }']`).oncontextmenu = item.fn
return true
}
const show = function(items, e, fnClose, fnCallback) {
//[自定义] delete old menu
let basicContextContainer = document.querySelector('.basicContextContainer');
if(basicContextContainer){
basicContextContainer.remove();
}
// Build context
let html = build(items)
// Add context to the body
document.body.insertAdjacentHTML('beforeend', html)
// Save current overflow and block scrolling of site
if (overflow==null) {
overflow = document.body.style.overflow
document.body.style.overflow = 'hidden'
}
// Cache the context
let context = dom()
// Calculate position
let position = getPosition(e, context)
// Set position
context.style.left = `${ position.x }px`
context.style.top = `${ position.y }px`
context.style.transformOrigin = `${ position.rx }px ${ position.ry }px`
context.style.opacity = 1
// Close fn fallback
if (fnClose==null) fnClose = close
// Bind click on background
context.parentElement.onclick = fnClose
context.parentElement.oncontextmenu = fnClose
// Bind click on items
items.forEach(bind)
// Do not trigger default event or further propagation
if (typeof e.preventDefault === 'function') e.preventDefault()
if (typeof e.stopPropagation === 'function') e.stopPropagation()
// Call callback when a function
if (typeof fnCallback === 'function') fnCallback()
return true
}
const visible = function() {
let elem = dom()
if (elem==null || elem.length===0) return false
else return true
}
const close = function() {
if (visible()===false) return false
let container = document.querySelector('.basicContextContainer')
container.parentElement.removeChild(container)
// Reset overflow to its original value
if (overflow!=null) {
document.body.style.overflow = overflow
overflow = null
}
return true
}
return {
ITEM,
SEPARATOR,
show,
visible,
close
}
});
//[自定义] 解决basicContextContainer出现系统菜单的bug
document.addEventListener('click', function(e){
if(basicContext) basicContext.close();
});
document.addEventListener('contextmenu', function(e){
if(['basicContextContainer','basicContext'].indexOf(e.target.className)!==-1) {
e.preventDefault();
return false;
}
});
//使用示例
// const clicked = function(e) {
// console.log(e.target.innerHTML);
// }
// document.querySelector('.my-context-menu-btn').addEventListener('contextmenu', function(e){
// const items = [
// { title: '新标签打开链接', extAttr: "data-name='new-blank'", fn: clicked },
// { },
// { title: '复制链接地址', extAttr: "data-name='copy-link'", fn: clicked },
// { title: '复制选中的文本', extAttr: "data-name='copy-text'", fn: clicked, disabled: true },
// { title: '复制响应数据', extAttr: "data-name='copy-response'", fn: clicked},
// { },
// { title: '复制为cURL格式', extAttr: "data-name='copy-curl'", fn: clicked},
// { title: '复制为fetch格式', extAttr: "data-name='copy-fetch'", fn: clicked},
// { title: '复制为await格式', extAttr: "data-name='copy-await'", fn: clicked},
// { title: '复制为xhr格式', extAttr: "data-name='copy-xhr'", fn: clicked},
// { title: '复制为分享链接', extAttr: "data-name='copy-share'", fn: clicked},
// { },
// { title: '删除该请求', extAttr: "data-name='del-request'", fn: clicked},
// { title: '删除所有请求', extAttr: "data-name='del-all-request'", fn: clicked }
// ]
// basicContext.show(items, e);
// });