跳到主要内容

使用 Tailwind CSS 选择元素

· 阅读需 1 分钟
1adybug
子虚伊人

在 Tailwind 中经常会有一些奇怪的类名,比如 w-[calc(100vw_-_64px)],这种类名是无法直接使用 document.querySelector 来选择的,需要进行一些处理,将类名的中 [](): 替换为 \\[\\]\\(\\)\\:,然后使用 document.querySelector 来选择。

className.replace(/([\[\]\(\):])/g, "\\$1")

AbortSignal 的用法

· 阅读需 1 分钟
1adybug
子虚伊人

对于 AbortSignal,我们常见的用法应该是用于取消 fetch 请求:

const controller = new AbortController()
const signal = controller.signal

fetch(url, { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error))

// 取消请求
controller.abort()

但是 AbortSignal 还有其他的用法,比如用于取消事件监听:

useEffect(() => {
const controller = new AbortController()
const signal = controller.signal
window.addEventListener("click", () => console.log("click"), { signal })
return () => {
controller.abort()
}
}, [])

当然,更高效的用法是取消多个事件监听

AbortSignal 类还有两个静态方法 AbortSignal.timeoutAbortSignal.any

AbortSignal.timeout 用于设置超时时间,在指定时间后会自动取消:

const signal = AbortSignal.timeout(1000)

类似于:

const controller = new AbortController()
const signal = controller.signal
setTimeout(() => controller.abort(), 1000)

AbortSignal.any 用于多个 AbortSignal 任意一个触发时取消:

const controller = new AbortController()
const controller2 = new AbortController()
const signal = AbortSignal.any([controller.signal, controller2.signal])

Nebula Graph 中的连续匹配

· 阅读需 2 分钟
1adybug
子虚伊人

先看两组 Cypher 语句:

MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
RETURN count(p)
MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
RETURN count(p)

通过运行结果可知两组语句的结果是一样的,可知连续匹配的作用域就是上一个匹配的结果。 uwm 是上一个匹配的结果,所以第二组语句中的 count(p) 与第一组语句中的 count(p) 是一样的。

如果我们把第二组语句改为:

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
MATCH p = (u:`user`)-[w2:watched]->(m:`movie`)
RETURN count(p)

那么此时,只有 um 是继承了上一次匹配的结果,w2 是重新匹配的结果,所以 count(p) 的结果就不一样了,也就是对于每一组 um 重新匹配了 w2 这条边,那么 count(p) 的结果必然是大于或者等于第一组语句的结果。

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
WITH u, m
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
RETURN count(p)

此时如果我们使用 WITH,那么 w 依然被丢失了,在第二次匹配中的 w 也相当于重新匹配的结果,所以 count(p) 的结果也是大于或者等于第一组语句的结果。

如果我们做以下改动也是错误的:

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
WITH u, m
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
RETURN count(p)

因为第一次匹配的结果并没有保证 um 之间的组合是独一无二的,也就是结果的列表中可能会存在系统的 um 组合,所以我们应该加一个语句:

MATCH (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
WITH u, m, count(w) as c
MATCH p = (u:`user`)-[w:watched]->(m:`movie`)
WHERE w.rate > 4
RETURN count(p)

通过 count(w) 就可以让 um 之间的组合是独一无二的

在 react 中手动触发 change 事件

· 阅读需 2 分钟
1adybug
子虚伊人

react 中存在很多受控组件,比如 inputtextarea 等,一般来说,受控组件将会是如下写法:

import { FC } from "react"

const App: FC = () => {
const [value, setValue] = useState("")

return <input value={value} onChange={e => setValue(e.target.value)} />
}

export default App

这种写法在 react 内部使用没有任何问题,但是对于外部使用者来说却存在很多问题,比如:

import { FC, useState } from "react"

const App: FC = () => {
const [value, setValue] = useState("")

return (
<div>
<div>
<label htmlFor="input">input:</label>
<input id="input" value={value} onChange={e => setValue(e.target.value)} />
</div>
<div>value:{value}</div>
</div>
)
}

export default App

当我们使用如下代码来更改 input 的值:

input.value = "123456"

却看到输入框的值发生了更改,下方的文本却没有任何变化,说明我们更改 input 的值的行为并没有触发 react 内部的 change 事件,那我们来手动触发 changeinput 事件:

const changeEvent = new Event("change", { bubbles: true })
const inputEvent = new Event("input", { bubbles: true })

input.dispatchEvent(changeEvent)
input.dispatchEvent(inputEvent)

可以看到,下方文本任然没有任何变化,Google 一番终于找到了正确答案 Simulate React On-Change On Controlled Components

const set = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set

set?.call(input, "123456")

const inputEvent = new Event("input", { bubbles: true })

input.dispatchEvent(inputEvent)

下方的文本成功同步!

在 Clash 中添加全局规则

· 阅读需 3 分钟
1adybug
子虚伊人

有时候我们希望某些网站不走代理,这时候就需要添加自定义规则,右击订阅,选择扩展脚本:

// Define main function (script entry)

/**
* @param {object} config
* @param {string[]} config.rules
* @param {string} profileName
*/
function main(config, profileName) {
config.rules.unshift("DOMAIN-SUFFIX,axshare.com,DIRECT")
config.rules.unshift("DOMAIN-SUFFIX,bing.com,DIRECT")
config.rules.unshift("DOMAIN-SUFFIX,codesandbox.io,DIRECT")
config.rules.unshift("DOMAIN-SUFFIX,csbops.io,DIRECT")
config.rules.unshift("DOMAIN-SUFFIX,csb.app,DIRECT")
config.rules.unshift("DOMAIN-SUFFIX,gallerycdn.vsassets.io,节点选择")
config.rules.unshift("DOMAIN-SUFFIX,gallery.vsassets.io,节点选择")
config.rules.unshift("DOMAIN-SUFFIX,marketplace.visualstudio.com,节点选择")
return config
}

DOMAIN 是指定域名,DOMAIN-SUFFIX 是指定域名后缀,DIRECT节点选择 都是规则内的分组。

规则类型匹配逻辑匹配目标性能等级关键注意事项
DOMAIN精确域名匹配域名仅匹配完全相同的域名字符串。
DOMAIN-SUFFIX域名后缀匹配域名匹配指定后缀及其所有子域名。
DOMAIN-KEYWORD域名关键词匹配域名匹配域名中任何位置出现的关键词,可能产生误匹配。
IP-CIDR目标 IPv4 地址段目标 IPv4默认触发 DNS 解析。
IP-CIDR6目标 IPv6 地址段目标 IPv6默认触发 DNS 解析。
GEOIP目标 IP 地理位置目标 IP基于 MaxMind 数据库;默认触发 DNS 解析。
SRC-IP-CIDR源 IPv4 地址段源 IPv4主要用于网关模式,区分局域网内不同设备。
DST-PORT目标端口目标端口基于 TCP/UDP 目标端口进行匹配。
SRC-PORT源端口源端口基于 TCP/UDP 源端口进行匹配。
PROCESS-NAME进程名称进程名操作系统相关;适用于 Windows, macOS, Linux, FreeBSD。
PROCESS-PATH进程路径进程路径操作系统相关;比进程名更精确,但可移植性差。
IPSETLinux IP 集目标 IP低 (内核)仅 Linux 可用;需要 ipset 工具;性能极高。

有时候我们希望某些规则对于所有的订阅都生效,这时候我们可以右击 全局扩展脚本,选择编辑文件:

// Define main function (script entry)

/**
* @param {object} config
* @param {string[]} config.rules
* @param {string} profileName
*/
function main(config, profileName) {
/** @type string | undefined */
const name = config["proxy-groups"]?.at(0)?.name
config.rules.unshift(`DOMAIN-SUFFIX,neo4j.com,${name}`)
return config
}

这里的意思找到订阅的第一个分组(一般都是通用的代理规则),然后将这条规则添加到这个分组中。

如果需要对于脚本进行调试,可以添加 console.log,在保存脚本以后,会在 全局扩展脚本 下方出现一个日志按钮,点击以后会输出脚本的日志。

stopPropagation 和 stopImmediatePropagation 之间的区别

· 阅读需 2 分钟
1adybug
子虚伊人

JavaScript 中,stopPropagationstopImmediatePropagation 是两个用于控制事件传播的方法。它们之间有一些关键的区别。

stopPropagation

stopPropagation 方法用于阻止事件冒泡到下一个元素,但允许其他事件处理程序在当前元素上继续执行。例如,如果你在一个按钮的点击事件中调用 stopPropagation,点击事件将不会冒泡到按钮的父元素,但该按钮上的其他点击事件处理程序仍然会被执行。

element.addEventListener("click", function (event) {
event.stopPropagation()
console.log("Button clicked")
})

stopImmediatePropagation

stopImmediatePropagation 方法不仅阻止事件冒泡,还阻止当前元素上所有后续的事件处理程序执行。这意味着一旦调用了 stopImmediatePropagation,当前元素上的其他事件处理程序将不会被触发。

element.addEventListener("click", function (event) {
event.stopImmediatePropagation()
console.log("Button clicked")
})

element.addEventListener("click", function (event) {
console.log("This will not be logged")
})

何时使用

  • 使用 stopPropagation 当你只想阻止事件冒泡但仍希望当前元素上的其他事件处理程序执行时。
  • 使用 stopImmediatePropagation 当你希望完全停止事件传播并阻止当前元素上的所有后续事件处理程序执行时。

了解这两个方法的区别可以帮助你更好地控制事件流,从而实现更复杂的用户交互逻辑。

修改函数的名称

· 阅读需 1 分钟
1adybug
子虚伊人

函数的名称在定义时已经被确定,比如:

function foo() {}

// 打印 "foo"
console.log(foo.name)

然而,匿名函数或者箭头函数的名称是空字符串:

const foo = (() => function () {})()

// 打印 ""
console.log(foo.name)

之所以要写成 (() => function () {})() 这么奇怪的形式,是应为使用 varletconst 定义的变量名会赋值给函数的名称:

const foo = function () {}

// 打印 "foo"
console.log(foo.name)

const bar = () => 0

// 打印 "bar"
console.log(bar.name)

变量定义时,属性名也会被赋值给函数的名称:

const obj = {
foo: function () {},
bar: () => 0,
}

// 打印 "foo"
console.log(obj.foo.name)

// 打印 "bar"
console.log(obj.bar.name)

然而,后续的赋值操作不会影响函数的名称:

const obj = {}

obj.foo = function () {}

// 打印 ""
console.log(obj.foo.name)

const name = "bar"
obj[name] = () => 0

// 打印 ""
console.log(obj.bar.name)

然而,更有趣的是,定义对象字面量时,动态的属性名会被赋值给属性名:

const name = "foo"

const obj = {
[name]: function () {},
}

// 打印 "foo"
console.log(obj[name].name)

最后,如果你想修改函数的名称,可以使用 Object.defineProperty

const foo = function () {}
Object.defineProperty(foo, "name", { value: "bar" })

// 打印 "bar"
console.log(foo.name)

在 Markdown 中使用 Prettier

· 阅读需 1 分钟
1adybug
子虚伊人

prettier 也支持 Markdown 文件的格式化,但是有以下注意点:

  1. 中文用户可以安装插件 prettier-plugin-lint-md
  2. JavaScript 代码块语言标识需要改为 javascript 或者 jsTypeScript 改为 typescript 或者 ts,驼峰命名的语言标识需要改为全小写,否则 prettier 无法识别
  3. tabWidth 要设置为 2,否则 Markdown 文件中的文本缩进会有问题

使用 Grid 布局实现的 Flow 组件

· 阅读需 1 分钟
1adybug
子虚伊人
.container {
display: grid;
grid-template-columns: repeat(auto-fill, 100px); /* 固定宽度为 100px */
grid-auto-rows: 100px; /* 固定高度为 100px */
gap: 10px; /* 网格间距 */
}

太牛了

selecto.js 配置项

· 阅读需 9 分钟
1adybug
子虚伊人

Selecto.js 是一个用于在网页上进行拖拽选择的 JavaScript 库,允许用户通过绘制选择框来选择页面上的元素。通过配置选项,您可以自定义 Selecto 的行为和功能。以下是每个配置选项的详细说明:

  1. container

    • 类型HTMLElement | string
    • 默认值document.body
    • 作用:指定选择区域的容器元素。Selecto 会在该容器内检测和选择元素。可以直接传入 DOM 元素或选择器字符串。
  2. dragContainer

    • 类型HTMLElement | string
    • 默认值container
    • 作用:指定触发拖拽事件的容器。通常用于在特定区域内启用或禁用拖拽选择。
  3. selectableTargets

    • 类型string[]
    • 默认值[]
    • 作用:定义可被选择的目标元素的选择器数组。只有匹配这些选择器的元素才会响应选择操作。
  4. hitRate

    • 类型number
    • 默认值100
    • 作用:设置元素被选择所需的覆盖率(百分比)。值为 0-100,表示选择框覆盖元素的比例达到该值时,元素即被选中。
  5. selectByClick

    • 类型boolean
    • 默认值true
    • 作用:是否允许通过单击来选择元素。设置为 true 时,用户可以直接点击元素进行选择。
  6. selectFromInside

    • 类型boolean
    • 默认值true
    • 作用:是否允许从被选择元素的内部开始拖拽选择。设置为 false 时,用户无法从目标元素内部开始绘制选择框。
  7. continueSelect

    • 类型boolean
    • 默认值false
    • 作用:是否在新的选择操作中保留之前选中的元素。设置为 true,新的选择结果会累加到已有的选择中。
  8. toggleContinueSelect

    • 类型string | string[]
    • 默认值null
    • 作用:指定用于切换 continueSelect 状态的按键。当按下指定的键时,continueSelect 状态会被激活或关闭。例如:'shift'['ctrl', 'meta']
  9. keyContainer

    • 类型HTMLElement | Document | Window
    • 默认值window
    • 作用:指定用于监听键盘事件的容器。当使用 toggleContinueSelect 功能时,需要监听键盘事件以切换选择模式。
  10. ratio

    • 类型number
    • 默认值0
    • 作用:设置选择框的固定宽高比。值为 0 时,不限制宽高比。非零值会使选择框按照指定比例缩放。
  11. scrollOptions

    • 类型object | null
    • 默认值null
    • 作用:配置自动滚动选项,当选择框到达容器边缘时,容器会自动滚动。包含以下属性:
      • container: 滚动的容器元素或选择器。
      • threshold: 触发滚动的距离阈值。
      • speed: 滚动速度。
      • getScrollPosition: 自定义获取滚动位置的方法。
  12. boundContainer

    • 类型HTMLElement | string | null
    • 默认值null
    • 作用:限制选择框的活动范围。选择框无法超出指定的容器边界。
  13. preventDefault

    • 类型boolean
    • 默认值false
    • 作用:是否在拖拽选择时调用 event.preventDefault(),以防止默认的浏览器行为(如文本选中、图片拖拽等)。
  14. cspNonce

    • 类型string
    • 默认值null
    • 作用:用于 Content Security Policy(内容安全策略)的 nonce 值,确保内联样式在启用了 CSP 的环境下被正确应用。
  15. checkInput

    • 类型boolean
    • 默认值false
    • 作用:是否在选择操作中检查输入元素(如 <input><textarea>)。设置为 true 时,选择操作不会影响这些输入元素的交互。
  16. preventDragFromInside

    • 类型boolean
    • 默认值true
    • 作用:是否防止从目标元素内部开始拖拽选择。设置为 true,可以避免与内部元素的拖拽操作冲突。
  17. getElementRect

    • 类型function
    • 默认值(el) => el.getBoundingClientRect()
    • 作用:自定义获取元素位置和尺寸的方法。可用于处理特殊情况或优化性能。
  18. dragCondition

    • 类型function
    • 默认值() => true
    • 作用:自定义拖拽开始的条件。返回 true 表示允许开始拖拽选择,false 则阻止拖拽。
  19. className

    • 类型string
    • 默认值''
    • 作用:为选择框元素添加自定义的 CSS 类名,方便进行样式定制。
  20. hoverClassName

    • 类型string
    • 默认值''
    • 作用:当元素被鼠标悬停或被选择时,添加到元素上的 CSS 类名。
  21. toggleContinueSelectWithoutDeselect

    • 类型boolean
    • 默认值false
    • 作用:在切换 continueSelect 状态时,是否保留已选中的元素而不取消选择。
  22. preventClickEvent

    • 类型boolean
    • 默认值true
    • 作用:是否在选择操作中阻止点击事件的触发,避免与其他点击交互冲突。
  23. appendTo

    • 类型HTMLElement | string
    • 默认值container
    • 作用:指定选择框元素添加到的容器。可用于调整选择框的层级关系。
  24. dragCondition

    • 类型function
    • 默认值null
    • 作用:自定义判断是否可以开始拖拽选择的条件函数。返回 true 允许拖拽,false 阻止拖拽。

示例代码:

const selecto = new Selecto({
container: document.querySelector(".selecto-area"),
dragContainer: window,
selectableTargets: [".selectable"],
hitRate: 50,
selectByClick: true,
selectFromInside: false,
continueSelect: false,
toggleContinueSelect: "shift",
keyContainer: window,
ratio: 0,
scrollOptions: {
container: document.querySelector(".scroll-container"),
threshold: 30,
speed: 10,
},
boundContainer: document.querySelector(".bound-area"),
preventDefault: true,
cspNonce: "your-csp-nonce",
checkInput: true,
preventDragFromInside: false,
getElementRect: el =>
// 自定义获取元素位置的方法
({
left: el.offsetLeft,
top: el.offsetTop,
width: el.offsetWidth,
height: el.offsetHeight,
}),
className: "custom-selecto",
hoverClassName: "selected",
toggleContinueSelectWithoutDeselect: true,
preventClickEvent: true,
appendTo: document.body,
dragCondition: e =>
// 仅当按下左键时允许拖拽选择
e.inputEvent.button === 0,
})

通过正确配置以上选项,您可以根据具体需求定制 Selecto.js 的功能,例如:

  • 多选功能:使用 continueSelect: true 实现多次选择累加。
  • 限制选择范围:通过 boundContainer 限制选择框的活动区域。
  • 自定义样式:使用 classNamehoverClassName 为选择框和被选中元素添加自定义样式。

注意事项:

  • 事件监听:Selecto.js 提供了丰富的事件回调,如 select, dragStart, dragEnd,可用于响应用户的选择操作。
  • 性能优化:在大量可选元素时,合理使用 selectableTargetsgetElementRect 以提升性能。

希望以上解释能帮助您全面了解 Selecto.js 的配置选项,从而更好地应用到项目中。

FAQ

dragContainercontainer 的区别

深入理解 selecto.js 中 containerdragContainer 的区别

selecto.js 中,containerdragContainer 都是用于配置选择行为的选项,但它们的作用不同。


1. container

  • 类型HTMLElement | string
  • 默认值document.body
  • 作用:定义选择操作的范围,以及可被选择的元素所在的区域。

container 是选择操作发生的区域,Selecto 会在这个容器内查找和选择元素。当您指定 selectableTargets 时,selecto.js 会在 container 内根据这些选择器查找目标元素。

container 的关键点:

  • 操作范围:它是 selecto.js 进行选择操作的主要区域。
  • 元素定位:所有可被选择的元素都应位于此容器内。
  • 坐标计算:选择框的位置和尺寸是基于该容器的坐标系计算的。
  • 默认容器:如果未指定,默认为 document.body

2. dragContainer

  • 类型HTMLElement | string
  • 默认值:如果未指定,则与 container 相同
  • 作用:指定监听鼠标或触摸事件以开始选择过程的区域或元素。

dragContainer 是用户可以开始拖拽以创建选择框的区域。换句话说,它是用户可以点击并拖动以开始选择的区域。

dragContainer 的关键点:

  • 事件监听区域:它是用于监听鼠标或触摸事件的区域。
  • 选择启动:用户只能在此区域内开始点击并拖动以进行选择。
  • 可独立设置:可以与 container 不同,允许在一个区域启动选择,但选择另一个区域内的元素。
  • 用途多样:可用于限制选择操作的启动区域,防止干扰其他交互。

区别和使用场景

区别总结:

  • container:定义选择操作的目标区域和元素所在位置。
  • dragContainer:定义用户可以开始拖拽选择的区域。

使用场景:

  1. 默认行为(containerdragContainer 相同)

    • 用户可以在容器内的任何位置开始拖拽选择。
    • 适用于简单的选择场景,没有特殊的交互限制。
  2. 不同的 containerdragContainer

    • 场景一:防止干扰元素的正常交互
      • 示例:在一个图片库中,您希望用户在图片上点击时查看大图,而不是开始选择。
      • 解决:将 dragContainer 设置为不包含图片的区域,如空白背景。
    • 场景二:从特定区域启动选择
      • 示例:只有在按住某个按钮或在特定面板上拖拽时才开始选择。
      • 解决:将 dragContainer 设置为该按钮或面板的元素选择器。

示例代码:

const selecto = new Selecto({
// 定义可被选择的元素所在的容器
container: ".items-container",
// 定义用户可以开始拖拽选择的区域
dragContainer: ".selection-area",
// 可被选择的元素
selectableTargets: [".items-container .item"],
// 其他配置...
})

解释:

  • .items-container:包含了所有可被选择的项目。
  • .selection-area:用户只能在此区域内开始拖拽选择。
  • 结果:避免了用户在与项目交互时(如点击、拖动项目)意外启动选择操作。

总结

  • container(容器):选择操作的范围,决定哪些元素可以被选择,以及选择框的位置计算。
  • dragContainer(拖拽容器):用户可以开始拖拽以进行选择的区域,决定了选择操作的触发区域。

通过正确配置这两个选项,您可以:

  • 控制选择行为:防止选择操作干扰其他交互(如点击、拖动元素本身)。
  • 优化用户体验:使选择操作更加直观,符合应用的交互逻辑。
  • 增强功能:实现复杂的交互需求,如在特定区域或条件下才允许选择。

建议:

  • 根据需求设置:如果不需要特殊的选择启动区域,保持默认配置即可。
  • 测试交互效果:在应用中测试不同配置,确保选择操作符合用户预期。

希望以上解释能帮助您充分理解 containerdragContainer 的区别,并在项目中灵活应用。