- 使用工具:微信小程序开发工具,cursor编码工具
- 技术方案: 通过微信小程序api获取手机设备名称,通过手机设备名称获取手机设备对应的ppi值,通过ppi值计算屏幕的一个像素代表多少毫米,提供给canvas绘制卡尺界面。支持滑动显示当前长度。
wxml页面内容是一个简单画布和一个标记
<!--pages/ruler/ruler.wxml-->
<view class="ruler-container">
<canvas type="2d" id="rulerCanvas" class="ruler-canvas" bindtouchmove="onTouchMove" bindtouchstart="onTouchStart" bindtouchend="onTouchEnd"></canvas>
<view class="selected-mark {{isMarked ? 'show' : ''}}" style="height: {{markPosition}}px;">
<view class="mark-line"></view>
<view class="mark-label">{{currentLength}}mm</view>
</view>
</view>
/* pages/ruler/ruler.wxss */
.ruler-container {
width: 100%;
height: 100vh;
background: #E5D3B3;
position: relative;
overflow: hidden;
background-image:
repeating-linear-gradient(
90deg,
rgba(139, 69, 19, 0.05) 0px,
rgba(139, 69, 19, 0.05) 2px,
transparent 2px,
transparent 20px
);
}
.ruler-canvas {
width: 100%;
height: 100vh;
background: transparent;
}
.measurement {
position: fixed;
top: 40rpx;
right: 40rpx;
background: rgba(0, 0, 0, 0.7);
color: #fff;
padding: 10rpx 20rpx;
border-radius: 10rpx;
font-size: 32rpx;
}
.selected-mark {
position: absolute;
left: 0;
top: 0;
width: 100%;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s;
display: flex;
align-items: center;
}
.selected-mark.show {
opacity: 1;
}
.mark-line {
width: 100%;
height: 100%;
background: rgba(0, 122, 255, 0.2);
border-top: 1px solid rgba(0, 122, 255, 0.5);
border-bottom: 1px solid rgba(0, 122, 255, 0.5);
}
.mark-label {
position: absolute;
right: 20px;
background: rgba(0, 122, 255, 0.9);
color: #fff;
padding: 4px 12px;
border-radius: 15px;
font-size: 14px;
transform: none;
white-space: nowrap;
}
.calibrate-btn {
position: fixed;
bottom: 40rpx;
right: 40rpx;
background: rgba(0, 122, 255, 0.9);
color: #fff;
padding: 20rpx 40rpx;
border-radius: 10rpx;
font-size: 28rpx;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
js中从接口获取设备PPI的接口需要自己写,因为微信小程序获取的手机型号没有一个标准,所有我的做法是用户带手机型号请求,如果查到就用具体的值,如果查不到我新建一个我后续补充。
// pages/ruler/ruler.js
Page({
/**
* 页面的初始数据
*/
data: {
currentLength: 0,
pixelRatio: 1,
ppi: 0,
calibrationValue: 1.0, // 校准系数
standardLength: 50, // 校准用标准长度(毫米)
isMarked: false,
markPosition: 0,
deviceInfo: {},
rulerLength: 100,
showTip: true
},
/**
* 生命周期函数--监听页面加载
*/
onLoad(options) {
this.initRuler();
},
async initRuler() {
const windowInfo = wx.getWindowInfo()
const deviceInfo = wx.getDeviceInfo()
// 获取设备信息
this.setData({ pixelRatio: windowInfo.pixelRatio });
// 先从本地获取PPI
this.getPPIFromStorage(deviceInfo.model);
},
// 从本地存储获取PPI
getPPIFromStorage(deviceName) {
try {
const ppi = wx.getStorageSync(`device_ppi_${deviceName}`);
if (ppi) {
// 本地存在PPI数据
this.setData({ ppi });
this.drawRulerWithCalibration();
} else {
// 本地无数据,从接口获取
this.getDevicePPI(deviceName);
}
} catch (e) {
console.error('读取本地PPI失败:', e);
// 读取失败也从接口获取
this.getDevicePPI(deviceName);
}
},
// 从接口获取设备PPI
getDevicePPI(deviceName) {
wx.request({
url: 'https://xxx.xxx.com/device-ppi/get-ppi',
method: 'GET',
data: {
deviceName: deviceName
},
success: (res) => {
if (res.data === 0) {
wx.showToast({
title: '未适配该手机型号',
icon: 'none',
duration: 2000
});
} else {
// 更新数据
this.setData({ ppi: res.data });
// 保存到本地
this.savePPIToStorage(deviceName, res.data);
this.drawRulerWithCalibration();
}
},
fail: (err) => {
console.error('获取PPI失败:', err);
wx.showToast({
title: '获取PPI失败',
icon: 'none'
});
}
});
},
// 保存PPI到本地存储
savePPIToStorage(deviceName, ppi) {
try {
wx.setStorageSync(`device_ppi_${deviceName}`, ppi);
} catch (e) {
console.error('保存PPI到本地失败:', e);
}
},
drawRulerWithCalibration() {
const query = wx.createSelectorQuery();
query.select('#rulerCanvas')
.fields({ node: true, size: true })
.exec((res) => {
const canvas = res[0].node;
const ctx = canvas.getContext('2d');
canvas.width = res[0].width * this.data.pixelRatio;
canvas.height = res[0].height * this.data.pixelRatio;
this.drawRuler(ctx, canvas.width, canvas.height);
});
},
drawRuler(ctx, width, height) {
const { pixelRatio, ppi, calibrationValue } = this.data;
// 计算每毫米对应的像素数
const pxPerMM = (ppi / 25.4) * calibrationValue;
ctx.clearRect(0, 0, width, height);
// 设置透明背景
ctx.fillStyle = 'rgba(255, 255, 255, 0.9)';
ctx.fillRect(0, 0, width * 0.5, height);
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.fillStyle = '#000';
ctx.lineWidth = pixelRatio;
// 计算可显示的总毫米数
const totalMM = Math.floor(height / pxPerMM);
// 绘制刻度
for (let i = 0; i <= totalMM; i++) {
const y = i * pxPerMM;
// 厘米刻度
if (i % 10 === 0) {
ctx.moveTo(0, y);
ctx.lineTo(width * 0.4, y);
ctx.font = `${14 * pixelRatio}px Arial`;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillText(`${i / 10}`, width * 0.45, y);
}
// 5毫米刻度
else if (i % 5 === 0) {
ctx.moveTo(0, y);
ctx.lineTo(width * 0.3, y);
}
// 1毫米刻度
else {
ctx.moveTo(0, y);
ctx.lineTo(width * 0.2, y);
}
}
ctx.stroke();
},
onTouchStart(e) {
const touchY = e.touches[0].clientY;
this.updateMark(touchY);
},
onTouchMove(e) {
const touchY = e.touches[0].clientY;
this.updateMark(touchY);
},
onTouchEnd() {
// 可选:是否在触摸结束后保持标记显示
// this.setData({ isMarked: false });
},
updateMark(touchY) {
const { ppi, calibrationValue } = this.data;
const pxPerMM = (ppi / 25.4) * calibrationValue;
// 计算实际毫米数
const mm = Math.floor(touchY * this.data.pixelRatio / pxPerMM);
this.setData({
currentLength: mm,
isMarked: true,
markPosition: touchY
});
},
onShareAppMessage() {
return {
title: '便捷标尺工具',
path: '/pages/ruler/ruler',
imageUrl: '/assets/images/ruler-icon.png'
}
},
onShareTimeline() {
return {
title: '便捷标尺工具',
query: '',
imageUrl: '/assets/images/ruler-icon.png'
}
}
})
{
"usingComponents": {},
"enablePullDownRefresh": false,
"disableScroll": true,
"navigationBarTitleText": "标尺工具"
}
以上内容除了从接口获取设备PPI接口,都有cursor编辑,我们只需要描述自己想要的效果。
在使用cursor的时候建议一小步一小步的修改更为精准
- [待优化] 在精确到小于1mm的刻度
最后附上我的小程序,可通过扫码体验效果。欢迎留言,提意见或建议。