0%

之前遇到了一个遮罩层级的奇怪问题。在dev环境能在弹窗前正常显示的全屏动画,在编译打包以后实际运行时跑到了弹窗的后面。
弹窗的z-index是500,按理说将动画放到501就应该能显示了,结果发现实际编译出来的z-index仅仅是1。
最后发现这个z-index被重新计算是cssnano的杰作。因为它只处理了项目本身的样式,而不包括第三方库的样式。

cssnano的配置如下:

1
2
3
4
5
"cssnano": {
"preset": "advanced",
"autoprefixer": false,
"postcss-zindex": false
}

可见z-index自动重新计算的配置应该是关闭的。
但是这配置实际上是从webpack2的旧项目上拷过来的,而新版本的cssnano已经悄悄修改了配置方式,这个写法等于什么都没配。
修改成以下配置就可以解决问题了:

1
2
3
4
5
6
"cssnano": {
"cssnano-preset-advanced": {
"zindex": false,
"autoprefixer": false
}
}

最近有个转盘抽奖的需求,搜了一下现有的轮子,有的是用jQuery的动画函数实现的,有的是用canvas绘图然后再用高频率的setTimeout调用旋转方法,前者太老了没法简单移植到vue项目,后者感觉性能表现可能不会太好。也有一些用CSS动画的方案,设计了加速-匀速-减速三个动画,再计算偏转角度让三个动画尽可能无缝衔接,但我感觉绕了大远路,应该有更简单轻量的实现方案。个人更倾向于用transition来实现,不过网上的例子感觉还不够好,有的倾斜文字都没有对齐,最后还是自己手写了一个。核心思路是用transition以及rotate实现旋转动画,使用transition-origin和rotate绘制出定位较为精确的轮盘奖项,同时支持动态设置奖品数量。

许多项目脚手架默认就会把src目录添加一个@别名,项目中实际引入时,虽然可以精简路径,但也带来一个很麻烦的问题:
IDE无法识别这些别名,因此导致无法自动完成路径、无法识别引用资源的输出、出现不必要的告警等情况。

偶然发现vscode的web项目里有一个jsconfig.json文件,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
},
"target": "ES6",
"module": "commonjs",
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}

只要有这个文件,vscode就可以正常识别出别名了。

后来发现JetBrains家的IDE更简单,配置指定一下就行:

在项目设置的webpack标签页里,将配置文件指向<projectRoot>/node_modules/@vue/cli-service/webpack.config.js即可。

保存并重新打开项目以后,不只src,所有的别名比如utils等等都可以被正常识别。

其实这都已经写在vue-cli3文档里了,只是原始表述不太直观,被我一直忽略了。

IOS下的webview页面,内嵌iframe元素,将其样式指定为宽高100%:

1
2
3
4
.iframe {
width: 100%;
height: 100%;
}

在安卓下运行均无问题,但是在IOS下会出现异常。

具体表现为iframe页面内的子元素一旦超出原先的边界,只要能影响到html元素的宽高,就会自动撑开iframe,即使html元素设置了overflow:hidden也没用。
比如一个body元素下的弹层需要从下往上滑动进场,这个弹层的位置就会导致html高度的变化,因此页面底部的tabbar就会在弹层运动期间先消失再出现。

解决方法就是使用具体的宽高数值锁定iframe元素:

1
2
3
4
5
6
7
8
function onLoadIFrame (index) {
// 修复IOS下轮播图初始化瞬间会让iframe宽度自行扩大问题
if (this.ENV.isIOS) {
const iframe = this.$el.querySelector('#iframe' + index)
iframe.style.width = iframe.clientWidth + 'px'
iframe.style.height = iframe.clientHeight + 'px'
}
}

babel-preset-env是现在最主流的babel解决方案,但是前阵子踩到了一个坑。
部分机型在执行到ES6的for of语句时就会报错。

原因有两个。

一是babel-preset-env默认只转换语法(syntax),而不会转换全局对象(IteratorGeneratorSetMapsProxyReflectSymbolPromise等)和全局对象上的新增方法(Object.assign等)。

二是for of会被babel编译,编译之后其中会出现Symbol关键字。

所以表面上看for of似乎可以使用,但实际上涉及到了新全局对象,从而导致不支持Symbol的浏览器报错。

因此,babel-preset-env并不像宣传(你需要的唯一Babel插件)描述的那样简单,它仍旧离不开那个笨重但好用的babel-polyfill

最终的解决方案是在.babelrc中的env下添加useBuiltIns属性, 值为entry:

1
2
3
4
5
6
"presets": [
["env", {
"modules": false,
"useBuiltIns": "entry"
}]
],

并在项目的主入口顶部添加:

1
import 'babel-polyfill'

这样env会自动精简没有使用到的polyfill,比单纯直接引入polyfill要小一些。

useBuiltIns还可以设置为usage,也就是在未兼容的环境使用新对象或方法时在当前脚本顶部自动添加polyfill。

以下问题均出现在windows环境,使用IntelliJ IDEA中的npm选项卡双击执行命令时。

使用npm的gh-pages工具包,可以很方便地一键发布指定目录到项目的gh-pages分支。
以前用着好好的,但是最近使用的时候,忽然发现以下报错信息:

1
2
No such device or address: '/dev/tty'
fatal: could not read Username for 'https://github.com': No error

网上搜到类似的问题都是hexo的发布,但这里并不像hexo一样用到config.yml,流传的处理方式也只是治标不治本(暴露了账号密码到配置文件中)。
关键问题还是执行过程中出现了windows不支持的语句。
最终处理方式是,重新安装git for windows,在安装过程这一步选择第二项:

这样可以使cmd支持git命令。应该是之前手贱升级git的时候点错了。

随后尝试一键发布到npm仓库时又出现了如下错误:

1
2
 Error: EPERM: operation not permitted, unlink '...\Temp\npm-xxxxx-xxxxxxxx\tmp\fromDir-xxxxxxxx\package.tgz'
npm ERR! at Error (native)

解决方式是打开Idea设置,找到Node.js and npm,将Package Manager从yarn改为npm。

9月初Chrome发布了最新的正式版,版本号61,升级以后却发现所有网页都变成了奶白清新配色。
不管CSS原本设置是什么颜色,最终显示效果都会比设置得更白一些。
如图:

操作系统是win10 x64,浏览器是chrome 61 x64。

去google了一下发现是普遍现象,解决方法如下:

  1. 在chrome地址栏输入chrome://flags/
  2. 找到配置项Color correct rendering,将其禁用并重启浏览器即可。

经验证问题已解决。

据说先行版本63没有此问题,也许后续版本中会修复,到时候可以将设置恢复默认。

使用vue-cli可以快速创建一个webpack项目脚手架,有相当多的项目都使用了相似的配置。
它们对发布环境的配置差不多是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'assets',
assetsPublicPath: '/',
productionSourceMap: false,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css']
},
...
}

这种配置只能通过根路径访问,比如http://project.example.com/

如果这个项目发布的内网地址是http://192.168.1.101/project/,那么它实际上是无法被直接访问的,所有js、css等资源的引用地址全部会出错。

从调试器中可以看到这些URL全都是写死成从根路径开始的。

而如果想要以http://www.example.com/project/来访问项目,就只有将assetsPublicPath修改成'/project/''并重新编译打包发布。

显然这种做法相当不灵活。

理想的做法是将项目以相对路径形式发布,无论它位于什么层级,都可以以相对路径的方式请求到引用的资源。

很多教程都是这么教的:

只要把根目录改为相对目录就好了

1
assetsPublicPath: './'

如果只是拿脚手架做点练习,这样做也许可以。

但只要你的项目有CSS引用了图片或者字体,并且它们会被归类到各自的目录,光是修改assetsPublicPath是不够的,会遇到这样的问题:

如果项目中用到了Bootstrap或者FontAwesome之类的工具,那这个坑是必然绕不过去的。

完美的解决方法是在cssLoaders中为CSS单独配置publicPath:

1
2
3
4
5
6
7
if (options.extract) {
return ExtractTextPlugin.extract({
use: sourceLoader,
fallback: 'vue-style-loader',
publicPath: '../../'
})
}

我的输出目录是dist > index.html + assets > css + fonts + img + js,因此publicPath需要向上返回两级,可根据实际项目配置修改它的值。

步骤总结:

  1. config/index.js中,将assetsPublicPath修改为./

  2. build/utils.js中,找到ExtractTextPlugin.extract,添加配置项publicPath: '../../'

v-viewer

用于图片浏览的Vue组件,支持旋转、缩放、翻转等操作,基于viewer.js

npm version

在线演示

使用示例

从0.x迁移

你需要做的唯一改动就是手动引入样式文件:

1
import 'viewerjs/dist/viewer.css'

安装

使用npm命令安装

1
npm install v-viewer

使用

引入v-viewer及必需的css样式,并使用Vue.use()注册插件,之后即可使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div id="app">
<!-- directive -->
<div class="images" v-viewer>
<img src="1.jpg">
<img src="2.jpg">
...
</div>
<!-- component -->
<viewer :images="images">
<img v-for="src in images" :src="src" :key="src">
</viewer>
</div>
</template>
<script>
import 'viewerjs/dist/viewer.css'
import Viewer from 'v-viewer'
import Vue from 'vue'
Vue.use(Viewer)
export default {
data() {
images: ['1.jpg', '2.jpg']
}
}
</script>

支持UMD方式引入

Browser

1
2
3
4
5
6
7
8
<link href="//path/viewer.css" rel="stylesheet">
<script src="//path/vue.js"></script>
<script src="//path/viewer.js"></script>
<script src="//path/v-viewer.js"></script>
...
<script>
Vue.use(VueViewer.default)
</script>

CommonJS

1
var VueViewer = require('VueViewer')

AMD

1
require(['VueViewer'], function (VueViewer) {});

以指令形式使用

只需要将v-viewer指令添加到任意元素即可,该元素下的所有img元素都会被viewer自动处理。

你可以像这样传入配置项: v-viewer="{inline: true}"

如果有必要,可以先用选择器查找到目标元素,然后可以用el.$viewer来获取viewer实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div id="app">
<div class="images" v-viewer="{movable: false}">
<img v-for="src in images" :src="src" :key="src">
</div>
<button type="button" @click="show">Show</button>
</div>
</template>
<script>
import 'viewerjs/dist/viewer.css'
import Viewer from 'v-viewer'
import Vue from 'vue'
Vue.use(Viewer)
export default {
data() {
images: ['1.jpg', '2.jpg']
},
methods: {
show () {
const viewer = this.$el.querySelector('.images').$viewer
viewer.show()
}
}
}
</script>

指令修饰器

static

添加修饰器后,viewer的创建只会在元素绑定指令时执行一次。
如果你确定元素内的图片不会再发生变化,使用它可以避免不必要的重建动作。

1
2
3
<div class="images" v-viewer.static="{inline: true}">
<img v-for="src in images" :src="src" :key="src">
</div>
rebuild

默认情况下当图片发生变更时(添加、删除或排序),viewer实例会使用update方法更新内容。

如果你遇到任何显示问题,尝试使用重建来代替更新。

1
2
3
<div class="images" v-viewer.rebuild="{inline: true}">
<img v-for="src in images" :src="src" :key="src">
</div>

以组件形式使用

你也可以单独引入全屏组件并局部注册它。

使用作用域插槽来定制你的图片展示方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div id="app">
<viewer :options="options" :images="images"
@inited="inited"
class="viewer" ref="viewer"
>
<template scope="scope">
<img v-for="src in scope.images" :src="src" :key="src">
{{scope.options}}
</template>
</viewer>
<button type="button" @click="show">Show</button>
</div>
</template>
<script>
import 'viewerjs/dist/viewer.css'
import Viewer from "v-viewer/src/component.vue"
export default {
components: {
Viewer
},
data() {
images: ['1.jpg', '2.jpg']
},
methods: {
inited (viewer) {
this.$viewer = viewer
},
show () {
this.$viewer.show()
}
}
}
</script>

组件属性

images
  • 类型: Array
trigger
  • 类型: Array

你可以使用trigger来代替images, 从而传入任何类型的数据。

trigger绑定的数据发生变更,组件就会自动更新。

1
2
3
<viewer :trigger="externallyGeneratedHtmlWithImages">
<div v-html="externallyGeneratedHtmlWithImages"/>
</viewer>
rebuild
  • 类型: Boolean
  • 默认值: false

默认情况下当图片发生变更时(添加、删除或排序),viewer实例会使用update方法更新内容。

如果你遇到任何显示问题,尝试使用重建来代替更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
<viewer
ref="viewer"
:options="options"
:images="images"
rebuild
class="viewer"
@inited="inited"
>
<template slot-scope="scope">
<img v-for="src in scope.images" :src="src" :key="src">
{{scope.options}}
</template>
</viewer>

组件事件

inited
  • viewer: Viewer

监听inited事件来获取viewer实例,或者也可以用this.refs.xxx.$viewer这种方法。

配置项 & 方法

请参考viewer.js.

插件配置项

name

  • 类型: String
  • 默认值: viewer

如果你需要避免重名冲突,可以像这样引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<template>
<div id="app">
<div class="images" v-vuer="{movable: false}">
<img v-for="src in images" :src="src" :key="src">
</div>
<button type="button" @click="show">Show</button>
</div>
</template>
<script>
import 'viewerjs/dist/viewer.css'
import Vuer from 'v-viewer'
import Vue from 'vue'
Vue.use(Vuer, {name: 'vuer'})
export default {
data() {
images: ['1.jpg', '2.jpg']
},
methods: {
show () {
const vuer = this.$el.querySelector('.images').$vuer
vuer.show()
}
}
}
</script>

defaultOptions

  • 类型: Object
  • 默认值: undefined

如果你需要修改viewer.js的全局默认配置项,可以像这样引入:

1
2
3
4
5
6
7
import Viewer from 'v-viewer'
import Vue from 'vue'
Vue.use(Viewer, {
defaultOptions: {
zIndex: 9999
}
})

你还可以在任何时候像这样修改全局默认配置项:

1
2
3
4
5
6
import Viewer from 'v-viewer'
import Vue from 'vue'
Vue.use(Viewer)
Viewer.setDefaults({
zIndexInline: 2017
})

vue-fullscreen

一个用于将任意页面元素进行全屏切换的vue组件

npm version

在线演示

使用示例

浏览器支持

Full Screen API

安装

使用npm命令安装

1
npm install vue-fullscreen

使用

引入vue-fullscreen,并使用Vue.use()注册插件,之后即可使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<div id="app">
<fullscreen ref="fullscreen" @change="fullscreenChange">
Content
</fullscreen>
<!-- deprecated
<fullscreen :fullscreen.sync="fullscreen">
Content
</fullscreen>
-->
<button type="button" @click="toggle" >Fullscreen</button>
</div>
</template>
<script>
import fullscreen from 'vue-fullscreen'
import Vue from 'vue'
Vue.use(fullscreen)
export default {
methods: {
toggle () {
this.$refs['fullscreen'].toggle() // recommended
// this.fullscreen = !this.fullscreen // deprecated
},
fullscreenChange (fullscreen) {
this.fullscreen = fullscreen
}
},
data() {
return {
fullscreen: false
}
}
}
</script>

注意: 由于浏览器的安全限制,全屏切换必须由一个用户操作事件发起,比如clickkeypress

注意: 因为watch回调现在只能异步执行,浏览器会拦截后续动作。我建议通过refs获取组件后直接调用其方法,而不要像旧版本那样修改传递给它的属性。

以插件形式使用

在Vue组件实例中,可以直接调用this.$fullscreen来获取全屏组件实例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<template>
<div id="app">
<div class="example">
Content
</div>
<button type="button" @click="toggle" >Fullscreen</button>
</div>
</template>
<script>
import fullscreen from 'vue-fullscreen'
import Vue from 'vue'
Vue.use(fullscreen)
export default {
methods: {
toggle () {
this.$fullscreen.toggle(this.$el.querySelector('.example'), {
wrap: false,
callback: this.fullscreenChange
})
},
fullscreenChange (fullscreen) {
this.fullscreen = fullscreen
}
},
data() {
return {
fullscreen: false
}
}
}
</script>

方法

toggle([target, options, force])

切换全屏模式。

  • target:
    • 类型: Element
    • 默认值: document.body
    • 全屏操作的目标元素。
  • options (可选):
    • 类型: Object
    • 配置项,详见下文。
  • force (可选):
    • 类型: Boolean
    • 默认值: undefined
    • 传入true可以指定进入全屏模式,效果与enter方法相同,false反之。

enter([target, options])

进入全屏模式。

  • target:
    • 类型: Element
    • 默认值: document.body
    • 全屏操作的目标元素。
  • options (可选):
    • 类型: Object
    • 配置项,详见下文。

exit()

退出全屏模式。

Options

callback

  • 类型: Function
  • 默认值: null

当全屏状态变更时执行。

fullscreenClass

  • 类型: String
  • 默认值: fullscreen

这个样式会在进入全屏状态时被添加到目标元素上。

wrap

  • 类型: Boolean
  • 默认值: true

如果设置为true,在进入全屏状态时目标元素会被一个div包裹, 你可以指定它的背景样式。

background

  • 类型: String
  • 默认值: #333

包裹元素的背景样式,只有在wrap为true且进入全屏时有效。

以组件形式使用

你可以单独引入全屏组件并局部注册它。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<template>
<div id="app">
<fullscreen ref="fullscreen" @change="fullscreenChange">
Content
</fullscreen>
<button type="button" @click="toggle" >Fullscreen</button>
</div>
</template>
<script>
import Fullscreen from "vue-fullscreen/src/component.vue"
export default {
components: {Fullscreen},
methods: {
toggle () {
this.$refs['fullscreen'].toggle()
},
fullscreenChange (fullscreen) {
this.fullscreen = fullscreen
}
},
data() {
return {
fullscreen: false
}
}
}
</script>

方法

toggle([force])

切换全屏模式。

  • force (可选):
    • 类型: Boolean
    • 默认值: undefined
    • 传入true可以指定进入全屏模式,效果与enter方法相同,false反之。

enter()

进入全屏模式。

exit

退出全屏模式。

Props属性

fullscreenClass

  • 类型: String
  • 默认值: fullscreen

全屏组件的样式类,只有全屏时才生效。

background

  • 类型: String
  • 默认值: #333

全屏组件的背景样式,只有全屏时才生效。

事件

change

  • isFullscreen: 当前的全屏状态。

在全屏状态改变时触发

避免重名冲突

如果你需要避免重名冲突,可以像这样引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<template>
<div id="app">
<fs ref="fullscreen">
Content
</fs>
<div class="example">
Content
</div>
<button type="button" @click="toggle" >Fullscreen</button>
</div>
</template>
<script>
import Fullscreen from 'vue-fullscreen'
import Vue from 'vue'
Vue.use(Fullscreen, {name: 'fs'})
export default {
methods: {
toggle () {
this.$refs['fullscreen'].toggle()
this.$fs.toggle(this.$el.querySelector('.example'), {
wrap: false,
callback: this.fullscreenChange
})
},
fullscreenChange (fullscreen) {
this.fullscreen = fullscreen
}
},
data() {
return {
fullscreen: false
}
}
}
</script>