当你的电脑没有装 ps,或电脑装了 ps 不能用的时候,网页版 ps 绝对是个好东西。对于我,不仅仅是网页版 ps,是苹果手机的套图。真的忘不掉。
记住的psd和数字
我知道,我用网页版 ps 打开 psd。然后将从苹果手机隔空过来的截屏图片拖入进对应图层。我先设置导入截屏图片的宽度为 756px,高度会自动调整。然后设置 x 坐标为 980px,y 坐标为 1262px。 最后点击菜单栏的文件,导出。就可以导出一张合图的苹果手机图了。
这些数字,也只是对我有用。这是 psd 决定的。
当你的电脑没有装 ps,或电脑装了 ps 不能用的时候,网页版 ps 绝对是个好东西。对于我,不仅仅是网页版 ps,是苹果手机的套图。真的忘不掉。
我知道,我用网页版 ps 打开 psd。然后将从苹果手机隔空过来的截屏图片拖入进对应图层。我先设置导入截屏图片的宽度为 756px,高度会自动调整。然后设置 x 坐标为 980px,y 坐标为 1262px。 最后点击菜单栏的文件,导出。就可以导出一张合图的苹果手机图了。
这些数字,也只是对我有用。这是 psd 决定的。
微信、百度小程序都支持常量 env,来自适应有圆弧屏幕的手机。头条小程序却不可以(抖音开发者官网也没有,只手搜适配,自适应等关键词没找到,看了部分文档也没找到)。头条自己的官方示范的小程序自己都没适配。这就是他们的态度。好吧,别人不做,自己总得做做好,要不体验肯定会差的。
像苹果这种棱角是圆弧的手机,底部顶部都是要做自适应处理,才可以有更好的体验。常规操作是加一个 margin
或 一个 padding
或补一个 div
。而这些都会用到这个常量: env(safe-area-inset-bottom)
比如可以这样做:
.safe-bottom {
margin-bottom: env(safe-area-inset-bottom);
}
# 或
.safe-bottom {
box-sizing: box-border;
padding-bottom: env(safe-area-inset-bottom);
}
# 或多加一个 div
.safe-bottom {
height: env(safe-area-inset-bottom);
}
甚至可以和 calc 组合使用, 比如:
.footer {
width: 100%;
height: calc(88rpx + env(safe-area-inset-bottom));
box-sizing: box-border;
padding-bottom: env(safe-area-inset-bottom);
}
可是,头条小程序中 env(safe-area-inset-bottom)
这玩意不支持。
既然一条路走不通,那就选其他的路。找找思路。比如只要能知道圆弧区域的高度不就可以了吗。在系统信息中,会携带这些信息(叫安全区域信息)。拿到了高度,一样可以 margin
,一样可以 padding
哈。
那梳理下流程。
globalProperties
中。之所以这样做,是因为需要自适应场景的页面有很多个,不想每个页面都再重复去获取这些信息。# main.js
import App from './App'
import { createPinia } from 'pinia'
import {
createSSRApp
} from 'vue'
export function createApp() {
const app = createSSRApp(App)
app.use(createPinia())
// 头条自适应
// #ifdef MP-TOUTIAO
const systemInfo = uni.getSystemInfoSync();
if (systemInfo && systemInfo.safeAreaInsets) {
app.config.globalProperties.$safeTop = systemInfo.safeAreaInsets.top;
app.config.globalProperties.$safeBottom = systemInfo.safeAreaInsets.bottom;
}
// #endif
return {
app
}
}
# 使用的地方
<view hover-class="hover" :style="{'margin-bottom': $safeBottom + 'px'}">
</view>
好吧,就这样完事了。
滑到底部了,不仅不自适应,底部还有一部分被挡住了。
其实,并不赞成官方实例的搞法。底部导航栏非原生,会对整个项目带来影响。页面滚动不丝滑是其中之一,滚动条会滚动到底部不好看。要不就是使用 scroolview 隐藏了滚动条。反正体验还是不怎么好。
初步了解了下 web3d 相关的信息。 three.js 或 babylon.js 要学一个(它们会封装 WebGL 和 WebGPU 的Api)。然后 blender 也要学一下。如果做虚拟城市这块的建模,building tools 这个插件很多人在用。那就用吧。
https://ranjian0.github.io/building_tools/
下载好后,打开 blender ,点击菜单栏的编辑,再点插件,再添加,把这个插件添加进去。
随着社会的发展,技术的发展,市场的发展,平面展示已不能满足需要了。商品可以 3D 化,道路建筑可以 3D 化。而 web 又是最通用的一种承载形式,web3D 的的必要性就突出来了。其实,很早就有了。我是萌新,可不能不去了解,甚至学习。
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGL_API
WebGL(Web 图形库)是一个 JavaScript API,可在任何兼容的 Web 浏览器中渲染高性能的交互式 3D 和 2D 图形,而无需使用插件。WebGL 通过引入一个与 OpenGL ES 2.0 非常一致的 API 来做到这一点,该 API 可以在 HTML5 canvas 元素中使用。这种一致性使 API 可以利用用户设备提供的硬件图形加速。
目前支持 WebGL 的浏览器有:Firefox 4+, Google Chrome 9+, Opera 12+, Safari 5.1+, Internet Explorer 11+ 和 Microsoft Edge build 10240+;然而,WebGL 一些特性也需要用户的硬件设备支持。
WebGL 2 API 引入了对大部分的 OpenGL ES 3.0 功能集的支持; 它是通过WebGL2RenderingContext界面提供的。
WebGL 是基于 OpenGL 演进的。
https://developer.mozilla.org/zh-CN/docs/Web/API/WebGPU_API
WebGPU 是 WebGL 的继任者,为现代 GPU 提供更好的兼容、支持更通用的 GPU 计算、更快的操作以及能够访问到更高级的 GPU 特性。
web3D应用 -> 引擎 -> web端图形API -> 本地端图形 API -> 显卡
WebGL 和 WebGPU 在这个环节中,本地图形 API 是不一样的。 WebGL 使用的是 OpenGL 或 DirectX。WebGPU 使用的是 Direct3D 或 Metal 或 Vulkan。
对于用户,只会关心所看到的 3D 效果。对于实现者,要了解知道渲染的流程还有熟悉 web 端图形 API 的使用。直接使用 WebGL API 或 WebGPU API 是能够实现 3D 效果。但会有第三方工具,比如 three.js 和 babylon.js 。它们会对 API 进行封装转换,可以省很多很多事。
blender 也是工具。
https://www.blender.org/
https://zhuanlan.zhihu.com/p/59575701
https://www.bilibili.com/video/BV13M41137Ki/?p=2&spm_id_from=pageDriver&vd_source=6993ce0cd88c9947ac7681b6864a99d0
https://zhuanlan.zhihu.com/p/369632693
https://juejin.cn/post/6966584226758000648
对于国内的开发生态,uniapp 的贡献可真不小。现在又推出 uts,有需要的是可以了解、学习、使用的。
uts,全称 uni type script,统一、强类型、脚本语言。
它可以被编译为不同平台的编程语言,如:
uts 采用了与 ts 基本一致的语法规范,支持绝大部分 ES6 API。
现有的 uni-app,仍以js引擎为主。但从HBuilderX 3.6开始,uni-app 支持 uts 插件(3.6支持vue3编译器,3.6.8支持vue2编译器)。
也就是 uts 的第一步不是完整开发一个独立的 app,而是作为 uni-app 的插件。后续 uts 会持续迭代,达到完整开发 app 的水平。
uts 插件编译到 app 平台时,在功能上相当于 uni-app 之前的 app 原生插件,也就是 Kotlin 和 Swift 开发的插件。
开发 uts 插件不需要熟悉 Kotlin 和 Swift 的语法,因为使用的是基于 ts 的语法。但需要熟悉 Android 和 iOS 的系统 API,否则无法调用原生能力。
在 HBuilderX 3.6 以前,uni-app 在 App 侧只有一种原生插件,即用 java 或 Objective-C 开发的插件。
在 uts 推出后,原来的 “App原生插件”,更名为 “App原生语言插件”。
不同的名字,代表它们需要开发者编写语言不同。但殊途同归,最后都编译为原生的二进制代码。
原生语言插件 | uts插件 | |
---|---|---|
开发语言 | java/oc | uts |
开发环境 | Android Studio/XCode | HBuilderX |
打包方式 | 外挂aar 等产出物 | 编译时生成原生代码 |
js层调用方式 | uni.requireNativePlugin() | 普通的js函数/对象,可以直接 import,支持摇树优化 |
相当于原生语言插件,uts插件的优势:
为了一些安全性,自己的电脑(本地)工具连接 mysql 的时候,我会限制它的 ip。可过一段时间自己的 ip 就会改变。这个时候,就连不上去。这个时候,就再修改下 ip,使其生效就好。
服务端
# 连接 mysql
mysql -uroot -p
# 输入秘密
xxxxxx
# 切换到数据库
use mysql
# 先查看查看 mysql 用户表里存了哪几个用户以及请求地址限制
select host,user from user;
# 可以看到 host 中有具体的 ip 的 还有 localhost。 这个 host 就是限制连接 mysql 服务的 ip。
# 假如之前设置外部能访问的 ip 是 138.xxx.xxx.xxx, 用户名是 xx_mysql
# 你新的 ip 是 136.xxx.xx.xx
# 修改
update user set host ='136.xxx.xx.xx' where user = 'xx_mysql';
# 生效
flush privileges;
头条、抖音、字节啥的都叫抖音小程序。没开始之前,以为和其他小程序一样,看这文档,噼里啪啦就能做下来。发现不是那么会事。不是不好开发,是一开始抖音就限制了你开发的能力。比如,你想自定义顶部导航栏不允许,比如企业级账号想获取手机号码不允许,比如获取定位不允许。作为一个小程序,一个常规的社交 app,这些是最基本的能力。这都不允许,怎么好把小程序体验开发到极致呢。
不是不允许,是要你满足它特定的条件,才渐渐的开放给你。不明白这是要搞啥子。好吧,只能妥协,开始开发出不是很满意的小程序出来。
顺便吐槽小各家小程序的顶部的胶囊。个人觉得还是微信和支付宝的好看一些。有些家不仅不好看,感觉就毫无设计可言。包括抖音的。胶囊丑,顶部导航栏也丑。
前边那个弹窗还有一些瑕疵,看得到的瑕疵。比如,使用贝塞尔曲线来模拟弹簧那种伸缩效果时,会露底。如果说是变魔术,用土话讲,那就是穿帮了。还有就是弹窗的动画的时间没有定义的地方。这次都加上了。先看看效果。
popupLayout.vue
<template>
<view v-if="controller.open" class="popup-layer" catchtouchmove="true">
<view v-if="direction == 'top'" class="top-bg-color" :class="{ 'top-bg-show': controller.bg, 'top-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'bottom'" class="bottom-bg-color" :class="{ 'bottom-bg-show': controller.bg, 'bottom-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'left'" class="left-bg-color" :class="{ 'left-bg-show': controller.bg, 'left-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'right'" class="right-bg-color" :class="{ 'right-bg-show': controller.bg, 'right-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view class="popup-content" :style="_location">
<slot></slot>
<view v-if="timingFun == 'cubic-bezier' && contentBgColor" :style="{background:contentBgColor}" :class="'content-bg-' + direction"></view>
</view>
</view>
</template>
<script setup>
import { reactive, defineProps, defineEmits, defineExpose, computed, onMounted, watch } from 'vue';
const controller = reactive({
open: false,
lock: false,
bg: false,
bgOpacity: 0,
translate: -100,
time: null
});
const emits = defineEmits(['onClose']);
const timingFuns = {
'cubic-bezier': 'transition-timing-function: cubic-bezier(.15,-0.88,.41,1.68);',
ease: 'transition-timing-function: easy;',
'ease-in': 'transition-timing-function: ease-in;',
'ease-out': 'transition-timing-function: ease-out;',
'ease-in-out': 'transition-timing-function: ease-in-out;',
linear: 'transition-timing-function: linear;'
};
const props = defineProps({
direction: {
type: String,
default: 'top' // 方向 top,bottom,left,right
},
bgOpacity: {
type: Number,
default: 0.38
},
timingFun: {
type: String,
default: 'cubic-bezier'
},
contentBgColor: {
type: String,
default: ''
},
transitionDuration: {
type: String,
default: '0.5s'
}
});
watch(() => props.timingFun, (newValue, oldValue) => {}, {
immediate: true
});
const _translate = computed(() => {
const transformObj = {
top: `transform:translateY(${-controller.translate}%)`,
bottom: `transform:translateY(${controller.translate}%)`,
left: `transform:translateX(${-controller.translate}%)`,
right: `transform:translateX(${controller.translate}%)`
};
return transformObj[props.direction];
});
const _location = computed(() => {
const positionValue = {
top: 'bottom:0px;width:100%;',
bottom: 'top:0px;width:100%;',
left: 'right:0px;top:0px;height:100%;',
right: 'left:0px;top:0px;height:100%;'
};
let style = `transition-duration:${props.transitionDuration};`;
if (timingFuns[props.timingFun]) {
style += timingFuns[props.timingFun];
}
return style + positionValue[props.direction] + _translate.value;
});
const open = function() {
if (controller.open) {
return;
}
controller.open = true;
let openTimer = setTimeout(() => {
clearTimeout(openTimer);
controller.lock = true;
controller.bg = true;
controller.bgOpacity = props.bgOpacity;
controller.translate = -0.1;
openTimer = null;
}, 100);
// 防止误触
let lockTimer = setTimeout(() => {
clearTimeout(lockTimer);
controller.lock = false;
lockTimer = null;
}, 500);
};
const close = function() {
if (controller.timer || controller.lock) {
return;
}
controller.bgOpacity = 0;
controller.bg = false;
controller.translate = -100;
controller.timer = setTimeout(() => {
clearTimeout(controller.timer);
controller.open = false;
controller.timer = null;
emits('onClose');
}, 500);
};
defineExpose({
open,
close
});
</script>
<style lang="scss" scoped>
.popup-layer {
width: 100vw;
height: 100vh;
overflow: hidden;
top: 0px;
left: 0px;
position: fixed;
z-index: 9999;
.popup-content {
position: absolute;
z-index: 1000000;
transition: transform;
}
.content-bg-top {
position: absolute;
width: 100%;
height: 108rpx;
left: 0;
bottom: -100rpx;
}
.content-bg-bottom {
position: absolute;
width: 100%;
height: 108rpx;
left: 0;
top: -100rpx;
}
.content-bg-left {
position: absolute;
width: 108rpx;
height: 100%;
top: 0;
right: -100rpx;
}
.content-bg-right {
position: absolute;
width: 108rpx;
height: 100%;
top: 0;
left: -100rpx;
}
}
// 底部出来
.top-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
left: -10vw;
top: 121vh;
opacity: 0;
transition: 0.24s opacity, 0.24s top;
}
.top-bg-show {
top: 0;
}
.top-bg-hide {
top: 121vh;
}
.top-bg-color:after {
content: '';
width: 120vw;
height: 120vw;
border-radius: 50%;
background-color: #000;
position: absolute;
left: 0;
top: -30vw;
}
// 顶部出来
.bottom-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
left: -10vw;
bottom: 121vh;
opacity: 0;
transition: 0.24s opacity, 0.24s bottom;
}
.bottom-bg-show {
bottom: 0;
}
.bottom-bg-hide {
bottom: 121vh;
}
.bottom-bg-color:after {
content: '';
width: 120vw;
height: 120vw;
border-radius: 50%;
background-color: #000;
position: absolute;
left: 0;
bottom: -30vw;
}
// 右边出来
.left-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
top: -10vh;
left: 121vw;
opacity: 0;
transition: 0.24s opacity, 0.24s left;
}
.left-bg-show {
left: 0;
}
.left-bg-hide {
left: 121vw;
}
.left-bg-color:after {
content: '';
width: 120vh;
height: 120vh;
border-radius: 50%;
background-color: #000;
position: absolute;
top: 0;
left: -30vh;
}
// 左边边出来
.right-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
top: -10vh;
right: 121vw;
opacity: 0;
transition: 0.24s opacity, 0.24s left;
}
.right-bg-show {
right: 0;
}
.right-bg-hide {
right: 121vw;
}
.right-bg-color:after {
content: '';
width: 120vh;
height: 120vh;
border-radius: 50%;
background-color: #000;
position: absolute;
top: 0;
right: -30vh;
}
</style>
在项目中,如果说什么组件用的最多。那弹窗觉得是第一个。想要一个好的弹窗,也是要花一些心思。这里从这几个方面来说。
只有想到了才会去做到,去实现。那么就一点点实现吧。
在实现这些效果中, css 占据了很重要的一部分。比如渐变、伸出。
实现渐变可以用 opacity、rgba。
想实现伸出弹出可以用 transform、keyframes,甚至绝对定位。
那想要一个好的弹射效果,先想到的可能会是 keyframes。它可以很精细的对目标的状态进行更改操作。可是吧,transform 可以使用 step 和 cubic-bezier(贝塞尔曲线),也可以实现弹簧的那种效果。这样实现起来就简单好多了(代码要少要少要干净),但是,觉得还是不够细腻,不够好。
先看看效果吧。
出了 css,其他就是 vue3 相关的东东了。下边直接贴出代码。
这里有个缺点,就是使用贝塞尔曲线来做弹出效果时候,底端会露白。其实,也想到了一个方法。传一个实体的背景色过来,在背景那边补上颜色也是可以解决的。
popupLayer.vue
<template>
<view v-if="controller.open" class="popup-layer" catchtouchmove="true">
<view v-if="direction == 'top'" class="top-bg-color" :class="{ 'top-bg-show': controller.bg, 'top-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'bottom'" class="bottom-bg-color" :class="{ 'bottom-bg-show': controller.bg, 'bottom-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'left'" class="left-bg-color" :class="{ 'left-bg-show': controller.bg, 'left-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view v-if="direction == 'right'" class="right-bg-color" :class="{ 'right-bg-show': controller.bg, 'right-bg-hide': !controller.bg }" :style="{ opacity: controller.bgOpacity }" @tap="close"></view>
<view class="popup-content" :style="_location"><slot></slot></view>
</view>
</template>
<script setup>
import { reactive, defineProps, defineEmits, defineExpose, computed, onMounted, watch } from 'vue';
const controller = reactive({
open: false,
lock: false,
bg: false,
bgOpacity: 0,
translate: -100,
time: null
});
const emits = defineEmits(['onClose']);
const timingFuns = {
'cubic-bezier': 'transition-timing-function: cubic-bezier(.15,-0.88,.41,1.68);',
ease: 'transition-timing-function: easy;',
'ease-in': 'transition-timing-function: ease-in;',
'ease-out': 'transition-timing-function: ease-out;',
'ease-in-out': 'transition-timing-function: ease-in-out;',
linear: 'transition-timing-function: linear;'
};
const props = defineProps({
direction: {
type: String,
default: 'top' // 方向 top,bottom,left,right
},
bgOpacity: {
type: Number,
default: 0.38
},
timingFun: {
type: String,
default: 'cubic-bezier'
}
});
watch(() => props.timingFun, (newValue, oldValue) => {}, {
immediate: true
});
const _translate = computed(() => {
const transformObj = {
top: `transform:translateY(${-controller.translate}%)`,
bottom: `transform:translateY(${controller.translate}%)`,
left: `transform:translateX(${-controller.translate}%)`,
right: `transform:translateX(${controller.translate}%)`
};
return transformObj[props.direction];
});
const _location = computed(() => {
const positionValue = {
top: 'bottom:0px;width:100%;',
bottom: 'top:0px;width:100%;',
left: 'right:0px;top:0px;height:100%;',
right: 'left:0px;top:0px;height:100%;'
};
let style = '';
if (timingFuns[props.timingFun]) {
style = timingFuns[props.timingFun];
}
return style + positionValue[props.direction] + _translate.value;
});
const open = function() {
if (controller.open) {
return;
}
controller.open = true;
let openTimer = setTimeout(() => {
clearTimeout(openTimer);
controller.lock = true;
controller.bg = true;
controller.bgOpacity = props.bgOpacity;
controller.translate = -0.1;
openTimer = null;
console.log('_location', _location);
}, 100);
// 防止误触
let lockTimer = setTimeout(() => {
clearTimeout(lockTimer);
controller.lock = false;
lockTimer = null;
}, 500);
};
const close = function() {
if (controller.timer || controller.lock) {
return;
}
controller.bgOpacity = 0;
controller.bg = false;
controller.translate = -100;
controller.timer = setTimeout(() => {
clearTimeout(controller.timer);
controller.open = false;
controller.timer = null;
emits('onClose');
}, 600);
};
defineExpose({
open,
close
});
</script>
<style lang="scss" scoped>
.popup-layer {
width: 100vw;
height: 100vh;
overflow: hidden;
top: 0px;
left: 0px;
position: fixed;
z-index: 9999;
.popup-content {
position: absolute;
z-index: 1000000;
transition: transform 0.4s;
}
}
// 底部出来
.top-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
left: -10vw;
top: 121vh;
opacity: 0;
transition: 0.24s opacity, 0.24s top;
}
.top-bg-show {
top: 0;
}
.top-bg-hide {
top: 121vh;
}
.top-bg-color:after {
content: '';
width: 120vw;
height: 120vw;
border-radius: 50%;
background-color: #000;
position: absolute;
left: 0;
top: -30vw;
}
// 顶部出来
.bottom-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
left: -10vw;
bottom: 121vh;
opacity: 0;
transition: 0.24s opacity, 0.24s bottom;
}
.bottom-bg-show {
bottom: 0;
}
.bottom-bg-hide {
bottom: 121vh;
}
.bottom-bg-color:after {
content: '';
width: 120vw;
height: 120vw;
border-radius: 50%;
background-color: #000;
position: absolute;
left: 0;
bottom: -30vw;
}
// 右边出来
.left-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
top: -10vh;
left: 121vw;
opacity: 0;
transition: 0.24s opacity, 0.24s left;
}
.left-bg-show {
left: 0;
}
.left-bg-hide {
left: 121vw;
}
.left-bg-color:after {
content: '';
width: 120vh;
height: 120vh;
border-radius: 50%;
background-color: #000;
position: absolute;
top: 0;
left: -30vh;
}
// 左边边出来
.right-bg-color {
width: 120vw;
height: 120vh;
background: #000;
position: relative;
top: -10vh;
right: 121vw;
opacity: 0;
transition: 0.24s opacity, 0.24s left;
}
.right-bg-show {
right: 0;
}
.right-bg-hide {
right: 121vw;
}
.right-bg-color:after {
content: '';
width: 120vh;
height: 120vh;
border-radius: 50%;
background-color: #000;
position: absolute;
top: 0;
right: -30vh;
}
</style>
测试调用的临时 vue
<template>
<view class="d-flex flex-column w-100">
<view class="flex-column mt-10 px-30">
<view class="text-color-3 text-size-30 font-w600">设置方向</view>
<view class="d-flex align-items-center flex-wrap mt-10">
<view v-for="(item, index) in directions" :key="index" class="mr-10 mt-10" hover-class="hover-opacity" @tap="setDirections(item)">
<view class="d-flex px-16 py-6 border-radius-8 flex-shrink-0" :class="{ 'bg-color-1 text-color-2': item == popupOption.direction, 'bg-color-2 text-color-4': item != popupOption.direction}">
<view class="text-size-26"> {{ item }} </view>
</view>
</view>
</view>
<view class="text-color-3 text-size-30 font-w600 mt-30">设置弹射</view>
<view class="d-flex align-items-center flex-wrap mt-10">
<view v-for="(item, index) in timingFuns" :key="index" class="mr-10 mt-10" hover-class="hover-opacity" @tap="setTimingFuns(item)">
<view class="d-flex px-16 py-6 border-radius-8 flex-shrink-0 action-item" :class="{ 'bg-color-1 text-color-2': item == popupOption.timingFun, 'bg-color-2 text-color-4': item != popupOption.timingFun}">
<view class="text-size-26"> {{ item}} </view>
</view>
</view>
</view>
<view hover-class="hover-opacity" class="bg-color-1 text-color-2 text-size-30 border-radius-16 py-20 mt-30 d-flex justify-content-center" @tap="doPopup">开始弹出</view>
</view>
</view>
<popupLayer ref="layer" :direction="popupOption.direction" :timing-fun="popupOption.timingFun" @onClose="onClose">
<view class="box-shadow-sm" :class="boxClass"></view>
</popupLayer>
</template>
<script setup>
import { ref, reactive } from 'vue'
const popupOption = reactive({
'direction': 'top',
'timingFun': 'cubic-bezier'
});
const directions = ['top', 'bottom', 'left', 'right'];
const timingFuns = ['cubic-bezier', 'ease', 'ease-in', 'ease-out', 'ease-in-out', 'linear'];
const layer = ref(null);
const boxClass = ref('bottom-box');
const setDirections = function(value) {
popupOption.direction = value;
boxClass.value = { 'bottom': 'top-box', 'top': 'bottom-box', 'left': 'right-box', 'right': 'left-box'}[value]
}
const setTimingFuns = function(value) {
popupOption.timingFun = value;
}
const doPopup = function() {
layer.value.open()
}
const onClose = function() {
console.log('close')
}
</script>
<style>
.bottom-box {
width: 100%;
height: 520upx;
border-top-left-radius: 20upx;
border-top-right-radius: 20upx;
background-color: #ffffff;
}
.top-box {
width: 100%;
height: 520upx;
border-bottom-left-radius: 20upx;
border-bottom-right-radius: 20upx;
background-color: #ffffff;
}
.left-box {
width: 520upx;
height: 100vh;
border-top-right-radius: 20upx;
border-bottom-right-radius: 20upx;
background-color: #ffffff;
}
.right-box {
width: 520upx;
height: 100vh;
border-top-left-radius: 20upx;
border-bottom-left-radius: 20upx;
background-color: #ffffff;
}
</style>
关于测试用的 vue,里边用到了很多 class,这些 class 都是全局定义好的。足够规范和足够包容就可以少写很多样式了。这样,有一个缺点就是 html 部分特别长。这就是组合起来的好处和缺点。也贴出部分代码。
base.scss
// 布局相关的样式
.d-none {
display: none;
}
// flex 布局(全用 flex 布局)
.d-flex {
display: flex;
}
.d-inline-flex {
display: inline-flex;
}
.flex-row {
flex-direction: row;
}
.flex-row-reverse {
flex-direction: row-reverse;
}
.flex-column {
flex-direction: column;
}
……
// margin
$list: 6 8 12 16 24;
// left
@each $i in $list {
.ml-#{$i} {
margin-left: #{$i}upx;
}
}
@for $i from 1 to 5 {
.ml-#{$i * 10} {
margin-left: #{$i * 10}upx;
}
}
// right
@each $i in $list {
.mr-#{$i} {
margin-right: #{$i}upx;
}
}
@for $i from 1 to 5 {
.mr-#{$i * 10} {
margin-right: #{$i * 10}upx;
}
}
// x
@each $i in $list {
.mx-#{$i} {
margin-left: #{$i}upx;
margin-right: #{$i}upx;
}
}
@for $i from 1 to 5 {
.mx-#{$i * 10} {
margin-left: #{$i * 10}upx;
margin-right: #{$i * 10}upx;
}
}
$num: 12;
@while $num < 54 {
.text-size-#{$num} {
font-size: #{$num}upx;
}
$num: $num + 2;
}
$num: 6;
@while $num < 30 {
.border-radius-#{$num} {
border-radius: #{$num}upx;
}
$num: $num + 2;
}
还有 css 的变量写法也要用起来。好东西就要用起来,有了变量,才算是真正的程序啊,哈哈。
root: {
--bg-color: #f00;
}
span {
background: var(--bg-color)
}
其实,这里没有对超过宽度或超过高度的处理。这里仅仅是个弹出的工具,仅仅做弹出而已。实体部分都由插槽交给需要用到的地方。
如果真遇到宽度或高度超过的情况,可以套一层 scroll-view 组件。如果实体内容具有可变性,就是有时候会高一点有时候会矮一点(数据驱动ui,数据来源服务器),这个时候给 scroll-view可以加个约束,比如 max-height:calc(100vh - 100px)
,具体多少,试着来。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/transition-timing-function
https://zhuanlan.zhihu.com/p/397089242
https://cubic-bezier.com/#.23,-0.47,.44,1.65
一切都是在发展,一切都是在进步。一切是如此,产品也是如此。 如果还停留在过去,产品的体验(服务、外观、交互、程序自身)就会变差,因为有了对比。这个时候,重构就很有必要了。
我是喜欢求新,求干净,愿意去学,愿意去使用。那么,开始选型和重来吧。
服务器:阿里g7 2核8G 6M 带宽 centos8 操作系统
服务器环境:php7.4 + nginx + mysql 8 + redis + …
服务端语言:php
服务端框架:laravel 8
后台框架: vue-element-admin (vue2)
网页版:blade + bootstrap 4
前端框架:uniapp + vue2
上架发布:微信小程序 + 苹果商店
辅助工具:postman + charles
服务器:阿里g7 2核8G 5M 带宽 Alibaba Cloud Linux release 3 操作系统
服务器环境:php8.2 + nginx + mysql 8 + redis + …
服务端语言:php
服务端框架:laravel 10
后台框架: 在选择中…
网页版:blade + bootstrap 5(当然也可以选择 Inertia )
前端框架:uniapp + vue3
上架发布:微信小程序 + 百度小程序 + 字节小程序 + 苹果商店
辅助工具:postman + charles
曾经也同时上架过百度小程序和微信小程序。现在要增加到发布四端了。如果用户用起来,web 端会做的更好,还会发布 android 端。上边那四端是一定要发的。
看似改变不大,其实很多地方都变了。包括一些写法。有云,条条大路通罗马,总有那么几条,那么一条是最好的。所以在选择的问题和使用的问题上很重要。
为什么还用 php。目前,服务端语言就 php 熟练一些。go 和 node.js 虽然使用过,也仅仅是使用过。太久没碰,也差不多丢了。 java 也能看懂一些,还是不够做一个完整的项目。php 有 laravel 框架,足够可以做很多很多事情了。
为什么用 uniapp 。uniapp 的确是个好东西。国内很多公司很多人都在用。一套代码,可以发布多个平台(多家小程序 + app + h5 + 快应用),而且支持 vue 的写法。如果没有特殊要求的 app,用 uniapp 来开发绝对是一个又快又好用的工具。如果有特殊需要,可以用 flutter。想当初,用 flutter 也干出个 app 出来。后来出了 dart2.0,更好了。
还有,如果有需要,可以开发桌面应用,比如使用 Electron,还有 Tauri。如果这两个都没用过,估计会选择 Tauri 吧。
为什么用 vue3。vue3 在性能和写法,还有一些新的功能特性上,都比 vue2 要强。有新的版本,为什么不用呢。总得前进吧。为什么不用 react 呢。用的东西太多太杂了,都记不住了。先不学 react 和 用它吧。