首页
Search
1
Linux 下 Bash 脚本 bad interpreter 报错的解决方法
69 阅读
2
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
51 阅读
3
Arch Linux 下解决 KDE Plasma Discover 的 Unable to load applications 错误
42 阅读
4
如何在 Clash for Windows 上配置服务
40 阅读
5
如何在 IOS Shadowrocket 上配置服务
40 阅读
clash
服务器
javascript
全部
游戏资讯
登录
Search
加速器之家
累计撰写
1,061
篇文章
累计收到
0
条评论
首页
栏目
clash
服务器
javascript
全部
游戏资讯
页面
搜索到
1061
篇与
的结果
2024-09-10
Gulp 使用命令行参数
命令行参数的好处就是方便控制,引用 Stack Overflow 的一个回答:The environment setting is available during all subtasks. So I can use this flag on the watch task too.通过 require('gulp-util').env 获得参数:var util = require('gulp-util'); gulp.task('styles', function() { return gulp.src(['src/styles/' + (util.env.theme ? util.env.theme : 'main') + '.scss']) .pipe(compass({ config_file: './config.rb', sass : 'src/styles', css : 'dist/styles', style : 'expanded' })) .pipe(autoprefixer('last 2 version', 'safari 5', 'ie 8', 'ie 9', 'ff 17', 'opera 12.1', 'ios 6', 'android 4')) .pipe(livereload(server)) .pipe(gulp.dest('dist/styles')) .pipe(notify({ message: 'Styles task complete' })); });就可以这么用:gulp watch --theme literature
2024年09月10日
5 阅读
0 评论
0 点赞
2024-09-10
深入 ES2015 默认参数
ES2015 为函数加入了方便的默认参数语法。function foo (x = 1) { // ... }很方便是吧,但每件新东西总有坑。TDZvar x = 1 function foo (x = x) { // ... } foo() // ReferenceError: x is not defined这个是最早见的坑,已经有不少文章指出了是个 TDZ (Temporal Dead Zone) 的问题。我找了大量资料,大部分文章基本是参考自这篇,它指出了 TDZ 但没有说明为什么。唯有这篇文章引用了规范,但论据和结论牛头对了马嘴。本着强迫症的精神,亲自去啃了一遍规范,现在把思路整理出来。其它关于 JavaScript 的文章可以看这里https://blog.crimx.com/tags/Understanding-JavaScript/。TDZ 基本先简单说一下什么是 TDZ。一般最开始见到这个词都是跟 let 和 const 挂钩。两者不会像 var 一样抬升,但它们又会占领了所在的作用域的整个部分,于是这个作用域在 let 和 const 声明之前的部分就会有一个死区,AKA 占着茅坑不拉屎。var x = 1 { let x = 2 // 这是 OK 的,这个作用域只有一个 x }var x = 1 { x = 4 // ReferenceError 茅坑已被占 let x = 2 }var x = 1 let x = 3 // SyntaxError 不能重复声明对作用域不太熟悉的可以参考一下我以前的一些笔记。默认参数 TDZ在规范 9.2.12 FunctionDeclarationInstantiation(func, argumentsList) 的步骤 27.c.i.2 可以看到:Let status be envRec.CreateMutableBinding(n).这是没有默认参数的情况,也就是 ES5 的做法。这个 mutable binding 顾名思义,就是 var 对应的绑定。在步骤 28.f.i.5.a 就说明了有默认参数怎么算:Let initialValue be envRec.GetBindingValue(n, false).这个 GetBindingValue 就是占茅坑的怂恿者:The concrete Environment Record method GetBindingValue for module Environment Records returns the value of its bound identifier whose name is the value of the argument N. However, if the binding is an indirect binding the value of the target binding is returned. If the binding exists but is uninitialized a ReferenceError is thrown, regardless of the value of S.所以带默认参数的时候才会有 TDZ 。x = x参数的处理在规范 9.2.12 FunctionDeclarationInstantiation(func, argumentsList) 的 23、24 和 25,将参数当做数组解构处理。在 IteratorBindingInitialization 中,参数按照各种情况处理。 x = x 属于 SingleNameBinding ,在 6.a 和 6.b 中 GetValue 会试图对右 x 取值(Initializer),从而触发 Reference Error 。a. Let defaultValue be the result of evaluating Initializer. b. Let v be GetValue(defaultValue).就像 let x = x 一样,先遇到左边的 x ,开始占茅坑。然后遇到等号,因为等号是右往左运算的,所以就继续看右边,发现了 x 。这是一个读取的行为,但这个作用域的 x 茅坑已经被左边的 x 占了,而且伦家还没完事呢。所以右边的 x 就踩了死区,引发 Reference Error 。中间作用域眼光锐利的朋友很可能发现了,我在前一步提到了作用域被占了,那么这个作用域是什么作用域?这是一个中间作用域,介于函数所在的作用域和函数内部的作用域。9.2.12 FunctionDeclarationInstantiation(func, argumentsList)NOTE 1 When an execution context is established for evaluating an ECMAScript function a new function Environment Record is created and bindings for each formal parameter are instantiated in that Environment Record. Each declaration in the function body is also instantiated. If the function’s formal parameters do not include any default value initializers then the body declarations are instantiated in the same Environment Record as the parameters. If default value parameter initializers exist, a second Environment Record is created for the body declarations. Formal parameters and functions are initialized as part of FunctionDeclarationInstantiation. All other bindings are initialized during evaluation of the function body.为什么为什么需要夹个新作用域?这主要是为了防止默认参数里面的表达式会被函数内部的变量污染。var x = true function foo (y = () => x) { var x = false return y() } foo() // true如果默认参数没有中间作用域,函数 () => x 就会跟 var x = false 共用一个作用域,x 就会被 shadow 掉,foo() 就会返回 false ,函数里面的变量泄露了,明显违背默认参数的本意。这个 bug 可以在 Firefox 51 之前的版本观察到(目前稳定版是 50.1.0)。解构参数解构 Destructured 也是相当好用的新家庭成员,还可以跟默认参数结合使用。function foo ({x = 1, y}) { return [x, y] } foo({ y: 5 }) // [1, 5]let [x = 1, y] = [] // x = 1, y = undefined ;[x = 1, y] = [3, 5] // x = 3, y = 5 ;({text: x = 1} = {}) // x = 1 ;({text: x = 1} = { text: 2 }) // x = 2这里的 {} 必须用括号括起来让它解释为表达式,不然会成为块声明。
2024年09月10日
5 阅读
0 评论
0 点赞
2024-09-10
倒腾 Vue Webpack2 单元测试
vue-cli 提供的官方模板确实好用。但一直用下来手贱毛病又犯了,像穿了别人的内衣,总感觉不舒服。所以有机会就瞎倒腾了一遍,总算把各个流程摸了一把。这里分享一下配置带覆盖率的单元测试。文件结构基本的文件结构。├─src │ ├─assets │ ├─components │ ├─app.vue │ └─main.js ├─test │ └─unit │ ├─coverage │ ├─specs │ ├─index.js │ └─karma.conf.js ├─.babelirc ├─webpack.conf.js └─package.json依赖根据需要增删yarn add -D \ cross-env \ # webpack webpack \ webpack-merge \ vue-loader \ # babel babel-core \ babel-loader \ babel-plugin-transform-runtime \ babel-preset-es2015 \ babel-register \ babel-plugin-istanbul \ # karma karma \ karma-coverage \ karma-phantomjs-launcher \ karma-sourcemap-loader \ karma-spec-reporter \ karma-webpack \ mocha \ karma-mocha \ sinon-chai \ karma-sinon-chai \ chai \ sinon \入口先从 package.json 开始。跟官方的一致。设置 BABEL_ENV 可以在测试的时候才让 Babel 引入 istanbul 计算覆盖率。{ "scripts": { "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run", "test": "npm run unit", } }配置 Babel在 .babelirc 中:{ "presets": ["es2015"], "plugins": ["transform-runtime"], "comments": false, "env": { "test": { "plugins": [ "istanbul" ] } } }按需设置,写 Chrome Extension 的话用 es2016 就行。Babel 的 istanbul 插件是个很聪明的做法。Loader 配置从 Vue Loader 的文档可以看到,不需要额外配置,它非常贴心自动识别 Babel Loader。如果还测试 js 文件那么给源文件夹下的 js 文件配置 Babel Loader 就行。以 src 为例:{ module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.js$/, loader: 'babel-loader', include: [ path.resolve(__dirname, '../src') ], exclude: /node_modules/ } ] } }Karma 配置为 webpack 设置环境// karma.conf.js const merge = require('webpack-merge') let webpackConfig = merge(require('../../webpack.conf'), { devtool: '#inline-source-map', plugins: [ new webpack.DefinePlugin({ 'process.env': '"testing"' }) ] }) // no need for app entry during tests delete webpackConfig.entry接着输出 karma 配置,基本沿用官方的配置。注意不同的浏览器需要安装不同的 karma 插件。// karma.conf.js module.exports = function (config) { config.set({ // to run in additional browsers: // 1. install corresponding karma launcher // http://karma-runner.github.io/0.13/config/browsers.html // 2. add it to the `browsers` array below. browsers: ['Chrome'], frameworks: ['mocha', 'sinon-chai'], reporters: ['spec', 'coverage'], files: ['./index.js'], preprocessors: { './index.js': ['webpack', 'sourcemap'] }, webpack: webpackConfig, webpackMiddleware: { noInfo: true }, coverageReporter: { dir: './coverage', reporters: [ { type: 'lcov', subdir: '.' }, { type: 'text-summary' } ] } }) }引入所有组件在 test/unit/index.js 里,这是官方的配置:// require all test files (files that ends with .spec.js) const testsContext = require.context('./specs', true, /\.spec$/) testsContext.keys().forEach(testsContext) // require all src files except main.js for coverage. // you can also change this to match only the subset of files that // you want coverage for. const srcContext = require.context('src', true, /^\.\/(?!main(\.js)?$)/) srcContext.keys().forEach(srcContext)因为之前配置 loader 时 src 文件夹下的 js 才会被收集计算覆盖率,所以可以放心 require 。第二段 require 是为了所有源码一起算覆盖率。可以看到官方配置只排除了 src 目录里的 main.js,如果是多入口可以用 /^(?!.*\/main(\.js)?$)/i 排除所有的 main.js 文件。开始测试这基本上就是所有的配置了。其它的设置应该都是围绕插件本身,就不赘述。Babeless 配置如果不需要 Babel 可以用 istanbul-instrumenter-loader 收集覆盖率。js 文件的配置同 Babel。 Vue 文件需要在 options.loaders 选项里为 js 补上:{ test: /\.vue$/, loader: 'vue-loader', options: { loaders: { 'js': 'istanbul-instrumenter-loader' } } }【完】
2024年09月10日
5 阅读
0 评论
0 点赞
2024-09-10
Position and Drag iframe
Position in iframesI wrote a Chrome extension Saladict, an inline translator, which involved such requirement: When user makes a text selection, something will pop up nearby the cursor.It looks simple at first view. Just listen to a mouseup event and get clientX and clientY from it.But there is a flaw in it - mouseup events inside iframes won't bubble up to the top frame.The solution is actually quite simple. If you know how to connect the dots.iframe script injectionUsing the all_frames property in manifest.json, a content script can run in all frames.{ "content_scripts": [ { "js": ["selection.js"], "matches": [""], "all_frames": true } ] }Mouse Event DetectionNow you can listen to mouseup event in all iframes.// selection.js document.addEventListener('mouseup', handleMouseUp)Upload Cursor CoordinatesclientX and clientY of the mouse events that are triggered in iframes are coordinates within iframe windows. Upload these coordinates as offsets to the upper frame, then plus the iframe position you will get the cursor position within the upper frame window.On Chrome you can boldly use postMessage.// selection.js function handleMouseUp (evt) { if (window.parent === window) { // Top frame doAwesomeThings(evt.clientX,evt.clientY) } else { // Pass the coordinates to upper frame window.parent.postMessage({ msg: 'SALADICT_CLICK', mouseX: evt.clientX, mouseY: evt.clientY }, '*') } }Add offsetsHow does the upper frame know which iframe is sending coordinates? Well, the message event contains the content window of the iframe. Use it to match the iframe element.// selection.js window.addEventListener('message', evt => { if (evt.data.msg !== 'SALADICT_CLICK') { return } let iframe = Array.from(document.querySelectorAll('iframe')) .filter(f => f.contentWindow === evt.source) [0] if (!iframe) { return } // calculate coordinates within current window let pos = iframe.getBoundingClientRect() let mouseX = evt.data.mouseX + pos.left let mouseY = evt.data.mouseY + pos.top if (window.parent === window) { // Top frame doAwesomeThings(mouseX, mouseY) } else { // Keep uploading window.parent.postMessage({ msg: 'SALADICT_CLICK', mouseX, mouseY }, '*') } })iframe DraggingAnother requirement for Saladict is to drag an iframe panel.Dragging 101Before getting into iframe dragging. There are few basic ideas of implementing a draggable element.One of the most common approaches is to listen to mousedown, mousemove and mouseup events, which handle drag start, dragging and drag end. And apply the offsets to the element's left and top style properties.If this is your first time implementing this feature, you are likely to listen to mousemove events of the element itself.You can indeed get the correct result in the way. The problem is, if the curser moves a bit too fast and leaves the element, the dragging will stop. That's why you should listen to global mousemove event instead.Dragging with iframeThe theory behind iframe dragging is the same. Only the mouse events triggered in iframes will not bubble up to the upper frame. You need to wrap it up yourselves.iframe PartDrag start is triggered by a draggable element inside iframe. For better performance, dragging and drag end event listeners are attached in drag start and are detached in drag end.Dragging event listener is required here because the mousemove event of the upper frame breaks inside the iframe. We need to let upper frame know what is happening inside iframe.// iframe.js var baseMouseX, baseMouseY $dragArea.addEventListener('mousedown', handleDragStart) function handleDragStart (evt) { baseMouseX = evt.clientX baseMouseY = evt.clientY window.parent.postMessage({ msg: 'SALADICT_DRAG_START', mouseX: baseMouseX, mouseY: baseMouseY }, '*') document.addEventListener('mouseup', handleDragEnd) document.addEventListener('mousemove', handleMousemove) } function handleMousemove (evt) { window.parent.postMessage({ msg: 'SALADICT_DRAG_MOUSEMOVE', offsetX: evt.clientX - baseMouseX, offsetY: evt.clientY - baseMouseY }, '*') } function handleDragEnd () { window.parent.postMessage({ msg: 'SALADICT_DRAG_END' }, '*') document.removeEventListener('mouseup', handleDragEnd) document.removeEventListener('mousemove', handleMousemove) }Upper Frame PartUse handleFrameMousemove to handle the offsets from iframe.// parent.js var pageMouseX, pageMouseY var frameTop = 0 var frameLeft = 0 $iframe.style.top = frameTop + 'px' $iframe.style.left = frameLeft + 'px' window.addEventListener('message', evt => { const data = evt.data switch (data.msg) { case 'SALADICT_DRAG_START': handleDragStart(data.mouseX, data.mouseY) break case 'SALADICT_DRAG_MOUSEMOVE': handleFrameMousemove(data.offsetX, data.offsetY) break case 'SALADICT_DRAG_END': handleDragEnd() break } }) function handleDragStart (mouseX, mouseY) { // get the coordinates within the upper frame pageMouseX = frameLeft + mouseX pageMouseY = frameTop + mouseY document.addEventListener('mouseup', handleDragEnd) document.addEventListener('mousemove', handlePageMousemove) } function handleDragEnd () { document.removeEventListener('mouseup', handleDragEnd) document.removeEventListener('mousemove', handlePageMousemove) } function handleFrameMousemove (offsetX, offsetY) { frameTop += offsetY frameLeft += offsetX $iframe.style.top = frameTop + 'px' $iframe.style.left = frameLeft + 'px' // Add the missing coordinates pageMouseX += offsetX pageMouseY += offsetY } function handlePageMousemove (evt) { frameTop += evt.clientX - pageMouseX frameLeft += evt.clientY - pageMouseY $iframe.style.top = frameTop + 'px' $iframe.style.left = frameLeft + 'px' pageMouseX = evt.clientX pageMouseY = evt.clientY }DemoYou can drag the iframe square below:Browser CompatibilityAs you can see, nothing fancy here, just passing coordinates around. So for older browsers, just use the old ways to communicate. You can also manipulate the values directly if they are same-origin.
2024年09月10日
2 阅读
0 评论
0 点赞
2024-09-10
JavaScript 音频变速保持音调
Playback Rate在浏览器的 Web Audio API 中其实已经提供了原生的接口来调整播放速度。但因为我们前面用了 Wavesurfer 显示波形,所以应该用它封装好的 setPlaybackRate(rate) 方法来调速。这里有坑需要注意,经测试浏览器在过低的速度会抛异常,我们需要做好边界控制。一般限制在 0.1 到 3 之间是比较保险,且超出这个范围一般也听不清了。数字编辑器为了让用户直观地调整速率,这里可以使用一个 React 组件 react-number-editor。它支持拖曳、快捷键与手动设置数字。Pitch Stretch就这么简单地实现了变速之后(你可以在文章开头的 CodeSandBox 例子中关闭最右的按钮 "Pitch Stretch" 把玩),你可能会发现,变速的同时声调也会发现奇怪的变化。放慢时声调变得很沉很恐怖,加快时声调变得很尖很搞笑。这是为什么呢?其实我是在没搞清原因的情况下实现了这个功能,但正好这周的 Wait But Why 谈到了 Everything You Should Know About Sound。其中一张图我认为非常适合解释了。可以看到,当我们把声音加快的时候,其实是把波长(Wavelength)压短了,于是产生了更高的音高(Pitch)。要想保持原来的音高,我们必须要把波长拉长,但因为整个的时间缩短了,所以中间必须要舍弃一些信息。反过来也一样,放慢的时候波长伸长了,我们要压短回正常,整个时长就不够了,需要凑一些额外的信息。应该增加或丢弃哪些部分根据使用场景的不同没有标准答案,所以就有各种拉伸(Pitch Stretch)的算法。目前找到的有 https://github.com/cutterbl/SoundTouchJS https://github.com/danigb/timestretch https://github.com/sebpiq/paulstretch.js https://github.com/echo66/OLA-TS.js https://github.com/echo66/PhaseVocoderJS https://github.com/Infinity/Kali https://github.com/0xfe/vexwarp https://github.com/mikolalysenko/pitch-shift https://github.com/GTCMT/pitchshiftjs 其中大部分都已经停止了维护,但还是能用的状态。因为 Wavesurfer 官方提供了 SoundTouch 的例子,所以我用的也是这个。但注意这个方式在 Firefox 下是有问题的,我目前是在 Firefox 下取消加载。如果你希望支持 Firefox ,echo66 的 PhaseVocoderJS 是个稳定的实现,输出质量也不错,但其项目组织有点散,我还没有精力捣腾。SoundTouch在 React 组件中跟前面 Wavesurfer 一样,我们也对 SoundTouch 进行懒加载。其中我们需要做的是同步 SoundTouch 和 Wavesurfer,有两个地方需要同步: 两者在同个位置播放。 两者使用一样的 Playback Rate。 同步位置因为 SoundTouch 基本没有文档,所以我也根据源码大概推导出整个的流程,并做了一些修改。先看初始化:import { SoundTouch, SimpleFilter, getWebAudioNode } from 'soundtouchjs' export default class Waveform extends React.PureComponent { shouldSTSync = false initSoundTouch = () => { const buffer = this.wavesurfer.backend.buffer const bufferLength = buffer.length const lChannel = buffer.getChannelData(0) const rChannel = buffer.numberOfChannels > 1 ? buffer.getChannelData(1) : lChannel let seekingDiff = 0 const source = { extract: (target, numFrames, position) => { if (this.shouldSTSync) { // get the new diff seekingDiff = ~~(this.wavesurfer.backend.getPlayedPercents() * bufferLength) - position this.shouldSTSync = false } position += seekingDiff for (let i = 0; i { const wavesurfer = WaveSurfer.create({ container: '#waveform-container', waveColor: '#f9690e', progressColor: '#B71C0C', plugins: [RegionsPlugin.create()] }) this.wavesurfer = wavesurfer wavesurfer.enableDragSelection({}) wavesurfer.on('region-created', region => { this.removeRegion() this.region = region }) wavesurfer.on('region-update-end', this.play) wavesurfer.on('region-out', this.onPlayEnd) wavesurfer.on('seek', () => { if (!this.isInRegion()) { this.removeRegion() } this.shouldSTSync = true }) wavesurfer.on('ready', this.play) wavesurfer.on('finish', this.onPlayEnd) }同步播放速率同步播放速率很简单,在设置 Wavesurfer 播放速率时同时设置 SoundTouch 的即可。this.wavesurfer.setPlaybackRate(speed) this.soundTouch.tempo = speed其中 tempo 是个 setter 故直接赋值即可。按需加载正如上面提到,播放时 extract 会被不停调用,性能相比默认情况当然会有些损失。我们前面已把初始化封装起来,接下来只需在速率变化时按需加载即可(速率默认为 1 无需加载)。updateSpeed = speed => { this.setState({ speed }) if (speed 3) { return } if (this.wavesurfer) { this.wavesurfer.setPlaybackRate(speed) if (speed !== 1 && this.state.pitchStretch && !this.soundTouch) {this.initSoundTouch(this.wavesurfer)} if (this.soundTouch) { this.soundTouch.tempo = speed } } this.shouldSTSync = true }同样,更新复原方法释放对象reset = () => { this.removeRegion() this.updateSpeed(1) if (this.wavesurfer) { this.wavesurfer.pause() this.wavesurfer.empty() this.wavesurfer.backend.disconnectFilters() } if (this.soundTouch) { this.soundTouch.clear() this.soundTouch.tempo = 1 } if (this.soundTouchNode) { this.soundTouchNode.disconnect() } this.soundTouch = null this.soundTouchNode = null this.shouldSTSync = false }现在播放音频,调整速率,可以听到声调保持正常了,但放慢的时候可能会有卡带的感觉,这是 SoundTouch 算法的缺陷。这点 echo66 的 PhaseVocoderJS 做得很棒,有精力一定看看如何整合进来。
2024年09月10日
5 阅读
0 评论
0 点赞
1
...
110
111
112
...
213