Navigator Clipboard 复制不生效

使用 navigator.clipboard.writeText 完成复制功能的实现时,在本地测试没有问题,部署后报错navigator.clipboard Cannot read property ‘writeText‘ of undefined

原因:Navigator API 的安全策略禁用了非安全域的 navigator.clipboard 对象,API 仅支持通过 HTTPS 提供的页面。为帮助防止滥用,仅当页面是活动选项卡时才允许访问剪贴板。活动选项卡中的页面无需请求许可即可写入剪贴板,但从剪贴板读取始终需要许可。

https://w3c.github.io/clipboard-apis/#dom-navigator-clipboard

解决:判断当前环境是否支持navigator clipboard API,不允许则使用 document.execCommand('copy')进行剪贴板交互。
使用兼容方案原因:navigator clipboard API是异步API,而使用document.execCommand('copy')进行剪贴板访问是同步的,只能读写 DOM,效率低下且在各浏览器之间还可能存在不同,在支持navigator clipboard API的情况下应尽量避免使用document.execCommand('copy')

传入DOM ID

export function copyCurrentTarget(text, id = '') {
    if (navigator.clipboard && window.isSecureContext) {
        navigator.clipboard.writeText(text)
    } else {
        window.getSelection().removeAllRanges()
        const questionToCopy = document.querySelector('#' + id)
        const range = document.createRange()
        range.selectNode(questionToCopy)
        window.getSelection().addRange(range)
        try {
            const successful = document.execCommand('copy')
            if (successful) {
                console.log('复制成功')
            } 
        } catch (error) {
            console.error(error)
        }
    }
}

视口外创建一个新的DOM,传入要复制的内容

function copyToClipboard(textToCopy) {
    if (navigator.clipboard && window.isSecureContext) {
        return navigator.clipboard.writeText(textToCopy)
    } else {
        let textArea = document.createElement("textarea")
        textArea.value = textToCopy
        textArea.style.position = "fixed"
        textArea.style.left = "-999999px"
        textArea.style.top = "-999999px"
        document.body.appendChild(textArea)
        textArea.focus()
        textArea.select()
        return new Promise((res, rej) => {
            document.execCommand('copy') ? res() : rej()
            textArea.remove()
        })
    }
}

扩展:安全和权限

复制和粘贴权限已添加到 Permissions API 中。当页面处于活动标签页时,会自动授予 clipboard-write 权限。 clipboard-read 权限必须手动请求。如果尚未授予权限,尝试读取或写入剪贴板数据的操作会自动提示用户授予权限。

const queryOpts = { name: 'clipboard-read', allowWithoutGesture: false };
const permissionStatus = await navigator.permissions.query(queryOpts);
// 'granted', 'denied' or 'prompt':
console.log(permissionStatus.state);

permissionStatus.onchange = () => {
  console.log(permissionStatus.state);
};

因为 Chrome 仅在页面是活动选项卡时才允许剪贴板访问,某些示例如果直接粘贴到 DevTools 中将无法运行,因为 DevTools 本身就是活动选项卡。有一个技巧:使用 setTimeout() 延迟剪贴板访问,然后在调用函数之前快速点击页面内部以将其聚焦:

setTimeout(async () => {
const text = await navigator.clipboard.readText();
console.log(text);
}, 2000);

要在 iframe 中使用 API,需要使用权限策略启用它

<iframe
    src="index.html"
    allow="clipboard-read; clipboard-write"
>
</iframe>

参考:https://stackoverflow.com/questions/51805395/navigator-clipboard-is-undefined、https://developer.chrome.com/blog/cut-and-copy-commands/、https://web.dev/async-clipboard/

判断对象为空

❌ 错误做法:

let obj = {}
obj == {} // false

✔ 正确做法:

const isObjEmpty = (obj) => !Reflect.ownKeys(obj).length && obj.constructor === Object;

注意:

  • 数组也可以使用Reflect.ownKeys方法,所以需要判断类型
  • 为什么不用Object.keys()Object.keys只能返回对象中可枚举的属性,Reflect.ownKeys返回所有属性
var obj = {
    a: 1,
    b: 2
}
Object.defineProperty(obj, 'method', {
    value: function () {
    alert("Non enumerable property")
},
    enumerable: false
})
​
console.log(Object.keys(obj))
// ["a", "b"]
console.log(Reflect.ownKeys(obj))
// ["a", "b", "method"]

层叠上下文和z-index

渲染过程和层叠顺序

浏览器将HTML解析为DOM的同时还创建了另一个树形结构,渲染树(render tree)。渲染树代表了每个元素的视觉样式和位置,同时决定浏览器绘制元素的顺序。

  • 通常情况下(没有使用定位),元素在HTML里出现的顺序决定了绘制顺序。后绘制的元素会出现在先绘制的元素前面。
  • 定位元素时,这种行为会改变。浏览器会先绘制所有非定位的元素,然后绘制定位元素。默认情况下,所有的定位元素会出现在非定位元素前面。

使用z-index控制层叠顺序

z-index属性的值可以是任意整数(正负都行)。z表示的是笛卡儿x-y-z坐标系里的深度方向。拥有较高z-index的元素出现在拥有较低z-index的元素前面。拥有负数z-index的元素出现在静态元素后面。

z-index的行为很好理解,但是使用它时要注意两个小陷阱。

  • z-index只在定位元素上生效,不能用它控制静态元素。
  • 给一个定位元素加上z-index可以创建层叠上下文。

层叠上下文

一个层叠上下文包含一个元素或者由浏览器一起绘制的一组元素。其中一个元素会作为层叠上下文的根,比如给一个定位元素加上z-index的时候,它就变成了一个新的层叠上下文的根。所有后代元素就是这个层叠上下文的一部分。

所有层叠上下文内的元素会按照以下顺序,从后到前叠放:

  • 层叠上下文的根
  • z-index为负的定位元素(及其子元素)
  • 非定位元素
  • z-indexauto的定位元素(及其子元素)
  • z-index为正的定位元素(及其子元素)

举个例子

下面这个例子可以用来理解层叠上下文,nested在第一个盒子的层叠上下文中,就算设置了很高的z-index,也会被第二个盒子遮挡。因为第一个盒子形成的层叠上下文在第二个盒子后面。

<body>
  <div class="box one positioned">
    one
    <div class="absolute">nested</div>
  </div>
  <div class="box two positioned">two
  </div>

</body>
body {
  margin: 40px;
}

.box {
  display: inline-block;
  width: 200px;
  line-height: 200px;
  text-align: center;
  border: 2px solid black;
  background-color: #ea5;
  margin-left: -60px;
  vertical-align: top;
}

.one { margin-left: 0; }
.two { margin-top: 30px; }


.positioned {        (以下5行)每个定位的盒子都创建了一个层叠上下文,z-index为1
  position: relative;               
  background-color: #5ae;           
  z-index: 1;                      
}                                  

.absolute {
  position: absolute;
  top: 1em;
  right: 1em;
  height: 2em;
  background-color: #fff;
  border: 2px dashed #888;              
  z-index: 100;       ←---- z-index只控制元素在它所处层叠上下文内的层叠顺序
  line-height: initial;
  padding: 1em;
}

表现结果

扩展到实际应用中的场景是,在已经打开的弹窗中再打开一个弹窗,就算第一个弹窗中的某个元素设置了很高的z-index,也还是会被第二个(后绘制)的弹窗挡住。

JetBrains Quest (March 11, 2020) Write-Up

第二波解谜,可以在 https://twitter.com/jetbrains/status/1237694815283879943 找到。

给出了一段字符串:

.spleh A+lrtC/dmC .thgis fo tuo si ti semitemos ,etihw si txet nehw sa drah kooL .tseretni wohs dluohs uoy ecalp a si ,dessecorp si xat hctuD erehw esac ehT .sedih tseuq fo txen eht erehw si ,deificeps era segaugnal cificeps-niamod tcudorp ehT

仔细看一下就会发现是倒序

比较明显是倒序的,先尝试复原:

fun main() {
    val string = ".spleh A+lrtC/dmC .thgis fo tuo si ti semitemos ,etihw si txet nehw sa drah kooL .tseretni wohs dluohs uoy ecalp a si ,dessecorp si xat hctuD erehw esac ehT .sedih tseuq fo txen eht erehw si ,deificeps era segaugnal cificeps-niamod tcudorp ehT"
    print(string.reversed())
}

会得到:The product domain-specific languages are specified, is where the next of quest hides. The case where Dutch tax is processed, is a place you should show interest. Look hard as when text is white, sometimes it is out of sight. Cmd/Ctrl+A helps.

比较明显:

来到 MPS: The Domain-Specific Language Creator by JetBrains,能发现这里有个:

打开报告,根据提示,全选来尝试看到白色的文字:

复制出来可以得到:This is our 20th year as a company, we have shared numbers in our JetBrains Annual report, sharing the section with 18,650 numbers will progress your quest.

所以前往 JetBrains 2019 Annual Highlights – Celebrating 20 Years!,找了半天没发现哪里有 18650,后面才发现原来是这个加起来刚好是 18650:

进去后慢慢翻,有个图片:

有一段火星文?可以慢慢看,也可以看看图片的 alt 属性:

能勉强看出来是:Did you know JetBrains is always hiring? Check out the kareers(careers) page and see if there is a job for you or a quest challenge to go further at least.

在招聘页面能找到这个 Fearless Quester 在 https://www.jetbrains.com/careers/jobs/fearless-quester-356/,但是忘记截图了,现在已经 404 了,有点尴尬。

一直觉得自己执行力好差啊…还是要想办法做到今日事今日毕才行…

根据提示后续要到 Game Development Tools by JetBrains,用科乐美秘技触发,有一个打砖块游戏,打完砖块就出现了:

Windows 10 下使用 Windows+Shift+S 截图

好消息,好消息!如果你正在使用较新的 Windows 10,那么截图只要:Windows 徽标键 +Shift+S 即可触发截图和草图应用来抓取矩形截图、任意形状截图、窗口截图或是全屏幕截图;Windows 徽标键 +PrtSc 即可触发截图和草图应用直接抓取全屏幕截图。不用再为了截图打开微信或 QQ 啦。

如果你觉得这个快捷键比较难按,你可以在设置->轻松使用->键盘中修改:

什么?你说我火星了?好像不是我,是我的朋友们…(无中生友ing)

好,来说说这个功能。根据此文章 Why doesn’t the screen clipping tool work anymore? – OneNote 所述,该热键本是 OneNote 用户用于截图的,自 Windows 10 创意者更新(也就是1703)后,由截图与草图应用接管。相关文章还包括:What’s New in Windows 10’s Creators UpdateHow to take and annotate screenshots on Windows 10

还要注意的是,根据 Snip & Sketch…. : Windows10 – Reddit。截取屏幕后,你通常直接从剪贴板中取得图片,但是在:%LOCALAPPDATA%\Packages\Microsoft.Windows.ShellExperienceHost_cw5n1h2txyewy\TempState\ScreenClip 路径下仍然保存有图片的副本(也就是 C:\Users\%USERNAME%\AppData\Local\Packages\Microsoft.Windows.ShellExperienceHost_cw5n1h2txyewy\TempState\ScreenClip)。可能有隐私泄露风险,请务必注意。

普通用户不需要 sudo 使用 Docker

大多数情况下,普通用户使用 Docker 都需要使用 sudo 进行提权,否则可能要切换到 root 用户才可直接使用。其实只需要将当前用户加入 docker 用户组即可。

The docker group grants privileges equivalent to the root user. For details on how this impacts security in your system, see Docker Daemon Attack Surface.

Post-installation steps for Linux | Docker Documentation

正常情况下应该已经存在docker用户组了,如果没有则需要:

sudo groupadd docker

来添加一个名为docker的用户组。

然后就可以将当前用户加入docker用户组:

sudo usermod -aG docker $USER

或者:

sudo gpasswd -a $USER docker

注销后重新登录即可生效。按文档,对于有图形界面的Linux,应该注销再登录即可,否则应该完全重启。如果不方便注销,可以尝试:

newgrp docker

此时应该可以不需要sudo来使用Docker了。

发现版本19.03起有一个实验性特性,以非root用户运行Docker守护进程:

dockerd-rootless.sh --experimental

引用

Post-installation steps for Linux | Docker Documentation
How can I use docker without sudo? – Ask Ubuntu
Docker security | Docker Documentation

Chrome 离线安装

推荐同学用 Google Chrome 作为日常的浏览器,但是他表示很难下载成功。为了省事,我决定直接弄个离线安装包给他。

直接搜索 chrome offline installer,找到了官方文档:下載及安裝 Google Chrome – 電腦 – Google Chrome說明。文中说明了在离线状态下安装 Chrome,需要在联网的主机上下载备用Chrome安装程序,就是我想要的离线安装包了。

只要打开上述链接再点击下载 Chrome 就可以得到离线安装包了。

仔细看了一下那个链接,其实本质上只是在 Chrome 的官方网址 https://www.google.com/chrome/ 后面加上 ?standalone=1 而已。即:https://www.google.com/chrome/?standalone=1

引用

Google Chrome 离线安装包的官方下载地址是什么? – 知乎