0%

使用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

有任何问题请到github项目页提交issue,博客的留言我可能无法及时看到,谢谢各位支持👍。

npm version language

npm version language

npm download license

v-viewer for vue3

在线演示

使用示例

从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
26
27
28
29
30
31
32
33
34
35
36
37
38
<template>
<div>
<!-- directive -->
<div class="images" v-viewer>
<img v-for="src in images" :key="src" :src="src">
</div>
<!-- component -->
<viewer :images="images">
<img v-for="src in images" :key="src" :src="src">
</viewer>
<!-- api -->
<button type="button" @click="show">Click to show</button>
</div>
</template>
<script>
import 'viewerjs/dist/viewer.css'
import VueViewer from 'v-viewer'
import Vue from 'vue'
Vue.use(VueViewer)
export default {
data() {
return {
images: [
"https://picsum.photos/200/200",
"https://picsum.photos/300/200",
"https://picsum.photos/250/200"
]
};
},
methods: {
show() {
this.$viewerApi({
images: this.images,
})
},
},
}
</script>

支持UMD方式引入

Browser

1
2
3
4
5
6
7
<link href="//unpkg.com/viewerjs/dist/viewer.css" rel="stylesheet">
<script src="//unpkg.com/vue/dist/vue.js"></script>
<script src="//unpkg.com/viewerjs/dist/viewer.js"></script>
<script src="//unpkg.com/v-viewer/dist/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
26
27
28
29
30
31
32
33
34
<template>
<div>
<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 { directive as viewer } from "v-viewer"
export default {
directives: {
viewer: viewer({
debug: true,
}),
},
data() {
return {
images: [
"https://picsum.photos/200/200",
"https://picsum.photos/300/200",
"https://picsum.photos/250/200"
]
};
},
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
35
36
37
38
39
40
<template>
<div>
<viewer :options="options" :images="images"
@inited="inited"
class="viewer" ref="viewer"
>
<template #default="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 { component as Viewer } from "v-viewer"
export default {
components: {
Viewer
},
data() {
return {
images: [
"https://picsum.photos/200/200",
"https://picsum.photos/300/200",
"https://picsum.photos/250/200"
]
};
},
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 #default="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这种方法。

以api形式使用

api形式只能使用modal模式。

你可以直接执行函数: this.$viewerApi({options: {}, images: []}) 来展现画廊, 而不需要自己来渲染这些img元素.

函数会返回对应的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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<template>
<div>
<button type="button" class="button" @click="previewURL">URL Array</button>
<button type="button" class="button" @click="previewImgObject">Img-Object Array</button>
</div>
</template>
<script>
import 'viewerjs/dist/viewer.css'
import { api as viewerApi } from "v-viewer"
export default {
data() {
sourceImageURLs: [
'https://picsum.photos/200/200?random=1',
'https://picsum.photos/200/200?random=2',
],
sourceImageObjects: [
{
'src':'https://picsum.photos/200/200?random=3',
'data-source':'https://picsum.photos/800/800?random=3'
},
{
'src':'https://picsum.photos/200/200?random=4',
'data-source':'https://picsum.photos/800/800?random=4'
}
]
},
methods: {
previewURL () {
// 如果使用`app.use`进行全局安装, 你就可以像这样直接调用`this.$viewerApi`
const $viewer = this.$viewerApi({
images: this.sourceImageURLs
})
},
previewImgObject () {
// 或者你可以单独引入api然后执行它
const $viewer = viewerApi({
options: {
toolbar: true,
url: 'data-source',
initialViewIndex: 1
},
images: this.sourceImageObjects
})
}
}
}
</script>

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
26
27
28
29
30
31
32
33
34
35
36
<template>
<div>
<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() {
return {
images: [
"https://picsum.photos/200/200",
"https://picsum.photos/300/200",
"https://picsum.photos/250/200"
]
};
},
methods: {
show () {
// viewerjs实例名称
const vuer = this.$el.querySelector('.images').$vuer
vuer.show()
// api名称
this.$vuerApi({
images: this.images
})
}
}
}
</script>

defaultOptions

  • 类型: Object
  • 默认值: undefined

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

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

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

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

vue-fullscreen

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

有任何问题请到github项目页提交issue,博客的留言我可能无法及时看到,谢谢各位支持👍。

npm version language

npm version language

npm download license

vue-fullscreen for vue3

在线演示

使用示例

支持

浏览器支持

注意: 在IE浏览器下使用需要先实现Promise的polyfill.

注意: Safari浏览器在桌面和iPad下支持,但iPhone不支持.

注意: 当处在全屏模式中,浏览其他页面,切换标签页,或者切换到其他应用(例如使用 Alt-Tab)也会导致退出全屏模式。

了解更多

从<=2.3.5版本迁移

组件

一般情况下你可以直接使用双向绑定来修改组件全屏状态了,无需直接调用组件的内部方法。

background属性已移除,你可以直接在组件上设置样式。

Api

wrapperbackground等与它相关的配置项已移除,它们使用场景有限,可定制性不强,而且你可以自己简单地实现它。

方法名更改如下:

enter request
support isEnabled
getState() isFullscreen

安装

使用npm命令安装

1
npm install vue-fullscreen

使用

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

组件和api会被一起安装到app全局。

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
36
37
38
39
<template>
<div>
<fullscreen :fullscreen.sync="fullscreen">
content
</fullscreen>
<button type="button" @click="toggle" >Fullscreen</button>
<div class="fullscreen-wrapper">
content
</div>
<button type="button" @click="toggleApi" >FullscreenApi</button>
</div>
</template>
<script>
import VueFullscreen from 'vue-fullscreen'
import Vue from 'vue'
Vue.use(VueFullscreen)
export default {
methods: {
toggle () {
this.fullscreen = !this.fullscreen
},
toggleApi () {
this.$fullscreen.toggle(this.$el.querySelector('.fullscreen-wrapper'), {
teleport: this.teleport,
callback: (isFullscreen) => {
this.fullscreen = isFullscreen
},
})
},
},
data() {
return {
fullscreen: false,
teleport: true,
pageOnly: false,
}
}
}
</script>

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

以api形式使用

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

或者你可以单独引入api然后执行它。

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
<template>
<div>
<div class="fullscreen-wrapper">
Content
</div>
<button type="button" @click="toggle" >Fullscreen</button>
</div>
</template>
<script>
import { api as fullscreen } from 'vue-fullscreen'
export default {
methods: {
toggle () {
fullscreen.toggle(this.$el.querySelector('.fullscreen-wrapper'), {
teleport: this.teleport,
callback: (isFullscreen) => {
this.fullscreen = isFullscreen
},
})
},
},
data() {
return {
fullscreen: false,
teleport: true,
}
}
}
</script>

方法 & 属性

toggle([target, options, force])

切换全屏模式。

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

request([target, options])

进入全屏模式。

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

exit()

退出全屏模式。

isFullscreen

判断是否处于全屏状态。

  • 类型: Boolean

注意: 唤起全屏的动作是异步的,在调用方法后你无法立即获取预期的结果。

isEnabled

判断环境是否支持全屏API。

  • 类型: Boolean

element

获取当前全屏的元素

  • 类型: Element | null

Options

callback

  • 类型: Function
  • 默认值: null

当全屏状态变更时执行。

fullscreenClass

  • 类型: String
  • 默认值: fullscreen

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

pageOnly

  • 类型: Boolean
  • 默认值: false

如果为true,不调用全屏API,而是将当前元素撑满网页。

注意: 如果浏览器不支持全屏API,这个选项默认值为true.

teleport

  • 类型: Boolean
  • 默认值: true

如果为true, 进入全屏时目标元素会被移动到document.body下。

这可以避免一些弹窗在全屏模式下看不到的问题。

以组件形式使用

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

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
<template>
<div>
<fullscreen :fullscreen.sync="fullscreen" :teleport="teleport" :page-only="pageOnly" >
Content
</fullscreen>
<button type="button" @click="toggle" >Fullscreen</button>
</div>
</template>
<script>
import { component } from 'vue-fullscreen'
export default {
components: {
fullscreen: component,
},
methods: {
toggle () {
this.fullscreen = !this.fullscreen
},
},
data() {
return {
fullscreen: false,
teleport: true,
pageOnly: false,
}
}
}
</script>

Props属性

fullscreen-class

  • 类型: String
  • 默认值: fullscreen

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

exit-on-click-wrapper

  • 类型: Boolean
  • 默认值: true

如果为true, 点击全屏组件的空白部分会退出全屏。

page-only

  • 类型: Boolean
  • 默认值: false

如果为true,不调用全屏API,而是将当前组件撑满网页。

注意: 如果浏览器不支持全屏API,这个选项默认值为true.

teleport

  • 类型: Boolean
  • 默认值: true

如果为true, 进入全屏时当前组件会被移动到document.body下。

这可以避免一些弹窗在全屏模式下看不到的问题。

事件

change

  • isFullscreen: 当前的全屏状态。

在全屏状态改变时触发。

插件配置项

name

  • 类型: String
  • 默认值: 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
32
33
34
35
36
37
38
39
<template>
<div>
<fs :fullscreen.sync="fullscreen" :teleport="teleport" :page-only="pageOnly" >
content
</fs>
<button type="button" @click="toggle" >Fullscreen</button>
<div class="fullscreen-wrapper">
content
</div>
<button type="button" @click="toggleApi" >FullscreenApi</button>
</div>
</template>
<script>
import VueFullscreen from 'vue-fullscreen'
import Vue from 'vue'
Vue.use(VueFullscreen, {name: 'fs'})
export default {
methods: {
toggle () {
this.fullscreen = !this.fullscreen
},
toggleApi() {
this.$fs.toggle(this.$el.querySelector('.fullscreen-wrapper'), {
teleport: this.teleport,
callback: (isFullscreen) => {
this.fullscreen = isFullscreen
},
})
},
},
data() {
return {
fullscreen: false,
teleport: true,
pageOnly: false,
}
}
}
</script>

如果有一组标题-内容形式的数据需要展现,用table感觉太笨重了一些,最符合语义化的做法应该是使用dl元素。
比如下面这个例子,展现了一个水平列表,样式定义来自bootstrap:

可以看到一个问题,当某些数据没有值时,它所在的dd元素高度会变成0,结果下面行的数据会顶上来,造成串行。

一种笨办法是在数据渲染时强制给所有dd元素一个&nbsp;,利用空格强行撑开元素高度。但是这种做法会影响页面处理逻辑,既麻烦又不整洁。

另一种做法是修改对齐方式:

表面上看是解决了问题,但是当缩小浏览器,使内容过长超出页面宽度导致换行时,就可以发现内容的文字跑到了标题那边,对强迫症来说很难受。

最完美的做法是使用CSS伪类为元素添加内容:

伪类的content属性如果填的是''或者&nbsp;都会被忽略,无法生效。所以这里用的是Unicode的零宽无连接符。\200c\200d分别是零宽无连接符和零宽连接符,分别用于阻止本来会连接的前后两个字符和强行合并本来不会连接的前后两个字符。它们的附加特性非常适合用在这种需要有字符但又希望它看不到的场合。