说到撩妹这个话题,估计很多人都觉得和程序员沾不上边,大多数人对程序员的印象是这样的:木讷,老实,内向,不爱社交。眼里只有代码,不懂浪漫!作为一个多年的程序员老砖员,我决定为广大程序员伙伴澄清这个谣言,告诉大家,我们程序员也是很浪漫的!
为了不让大家痛失女神的芳心,我做了一个表白神器,在此和各位程序员小哥哥们分享分享!最终赢取白富美,走上人生巅峰!
首先看看效果。扫描下方二维码即可预览
【看看代码的实现】
一、写代码前首先需要考虑以下几点
- 兼容不同手机分辨率
- 采用CSS3动画,更流畅
- 文字的定位采用一个算法,将其竖排居中显示
- 后期的可拓展性
二、代码之工具方法
我们需要一些工具方法,将之抽离出来以便公用,最好不要污染业务代码,这里用了一个词“污染”,在我们做项目的时候,其实很多代码都是和业务无关的,如果都写到一起了,这样会让我们的业务非常不清晰,之所以要抽离出来,就是为了让我们的业务逻辑清晰可见,当抽离出来后,你会发现,业务代码就几行,看上去非常的简洁。
以下是我抽离的非业务代码
/**
*
* @desc 生成指定范围随机数
* @param {Number} min
* @param {Number} max
* @return {Number}
*/
export function randomNum(min, max) {
return Math.floor(min + Math.random() * (max - min));
}
/**
* @desc 数组打乱顺序
*/
export function randomArrSort(arr) {
arr.sort(() => 0.5 - Math.random());
return arr;
}
/**
*
* @desc 随机生成颜色
* @return {String}
*/
export function randomColor() {
return '#' + ('00000' + ((Math.random() * 0x1000000) << 0).toString(16)).slice(-6);
}
/**
* 生成一个用不重复的ID
*/
export function getRandomID(randomLength = 8) {
return 'id_' + Number(
Math.random()
.toString()
.substr(3, randomLength || 8) + Date.now()
).toString(36);
}
/**
* @desc 设置自动适配的尺寸
*/
export function setSize($box, scale, fixed) {
let width = fixed ? appWidth : $box.width(),
height = fixed ? appHeight : $box.height();
const { innerWidth, innerHeight } = window;
let top = (innerHeight - height * scale) / 2;
if (top < 0) {
top = 0;
}
$box.css({
left: (innerWidth - width * scale) / 2,
top: top,
transform: `scale(${scale})`
});
}
/**
* @desc 计算sacle 和 偏移
*/
export function getScale() {
const width = 320;
const height = 514;
// 自动适配
const { innerWidth, innerHeight } = window;
// 假设宽度适配 scale * width = innerWidth
let scale1 = innerWidth / width;
// 假设高度适配 scale * height = innerHeigh
let scale2 = innerHeight / height;
return scale1 > scale2 ? scale2 : scale1;
}
三、抽取公共类
其实我们的文字可以单独抽离成一个类进行管理,这个类需要包含文字相关的一些方法
1、获取随机文案
2、获取全部文字
3、渲染全部的文字
4、执行文字动画
5、计算目标文案的位置
6、寻找目标文字的位置,然后clone一个
7、初始化
8、重新执行
我大致评估了,需要这些方法。
所以文字的类构造如下:
import * as tool from '../utils/tools';
import { texts } from './texts';
/**
* @desc 文字的方法
*/
export default class Text {
constructor(set) {
this.set = Object.assign(
{
target: '#phone',
width: 320,
height: 514,
callback: () => {}
},
set
);
this.dom = $(set.target);
}
// 获取随机文案
getText() {
const index = tool.randomNum(0, texts.length - 1);
const t1 = texts[index];
return t1.split('');
}
// 获取全部文字
getAllText() {
let all = [];
const { width, height } = this.set;
texts.forEach(d => {
// let str = d.replace(/[,,。,?,!,……,~,:”,“,\s]/gm, '');
all = [...all, ...d.split('')];
});
// 去重
all = Array.from(new Set(all));
all = all.map(text => {
const a = tool.randomNum(5, 10);
const iskey = this.targetText.indexOf(text) === -1 ? false : true;
return {
id: tool.getRandomID(),
y: height / 2 - a / 2,
x: width / 2 - a / 2,
opacity: Math.random() * 0.5,
scale: Math.random() * 1.2,
iskey,
width: a,
height: a,
text
};
});
return tool.randomArrSort(all);
}
// 渲染allText
renderTexts(arr) {
let shtml = '';
arr.forEach(d => {
const { id, x, y, scale, opacity, iskey, width, height, text } = d;
shtml += `<span id="${id}" class="${
iskey ? 'text text-active' : 'text'
}" style="width: ${width}px; height: ${height}px; transform: translate(${x}px, ${y}px) scale(${scale}); opacity: ${opacity};">${text}</span>`;
});
this.dom.append(shtml);
}
// 计算目标文字的位置
getTargetCoord(targetText) {
const tlen = targetText.length;
let val = 10; // 10个换行
let size = 20,
arr = [],
boxWidth = Math.ceil(tlen / val) * size,
boxHeight = size * val; // 10个字换行
const { width, height } = this.set;
// 坐标起点
const start = {
x: (width - boxWidth) / 2,
y: (height - boxHeight) / 2 - 100
};
for (let i = 0; i < tlen; i++) {
let a = Math.floor(i / val);
arr.push({
width: size,
height: size,
x: start.x + a * size,
y: start.y + (i - a * val) * size
});
}
return arr;
}
// 找到对应的字,然后clone一个对象
cloneTargetStyle(d, tArr) {
const obj = tArr.filter(a => {
return a.text === d;
})[0];
obj.id = tool.getRandomID();
return { ...obj };
}
// 目标文字动画
targetTextAimate() {
let index = 0;
let tArr = [];
this.allText.forEach(d => {
if (d.iskey) {
tArr.push(d);
}
$(`#${d.id}`).css({
opacity: 0
});
});
// 获取目标数组
const targetArr = [];
this.targetText.forEach(d => {
targetArr.push(this.cloneTargetStyle(d, tArr));
});
// 设置坐标
const arr = this.getTargetCoord(targetArr);
// 渲染dom
this.renderTexts.bind(this)(targetArr);
targetArr.forEach((d, index) => {
let item = arr[index];
$(`#${d.id}`).css({
opacity: 1,
width: item.width,
height: item.height,
transform: `translate(${item.x}px, ${item.y}px) scale(1)`
});
});
setTimeout(() => {
this.set.callback();
}, 3000);
}
// allText 文字动画
allTextAnimate() {
const { width, height } = this.set;
let count = 0;
const doAnimate = () => {
count++;
this.allText = this.allText.map(d => {
d.y = tool.randomNum(0, height);
d.x = tool.randomNum(0, width);
d.scale = Math.random() * 1.5;
// d.opacity = Math.random() * 0.5;
return d;
});
this.allText.forEach(d => {
const { x, y, scale } = d;
$(`#${d.id}`).css({
transform: `translate(${x}px, ${y}px) scale(${scale})`
});
});
};
const runTime = () => {
if (count > 2) {
setTimeout(() => {
this.targetTextAimate.bind(this)();
}, 3000);
return;
}
setTimeout(() => {
doAnimate();
runTime();
}, 3000);
};
doAnimate();
runTime();
}
// 重新执行
restart = () => {
this.dom.empty();
this.targetText = this.getText();
this.allText = this.getAllText.bind(this)();
this.renderTexts.bind(this)(this.allText);
setTimeout(() => {
this.allTextAnimate.bind(this)();
}, 10);
};
// 初始化
init = () => {
// 获取文案
this.targetText = this.getText();
this.allText = this.getAllText.bind(this)();
// 渲染文字
this.dom.addClass('h5ds-text7');
this.renderTexts.bind(this)(this.allText);
setTimeout(() => {
this.allTextAnimate.bind(this)();
}, 0);
};
}
四、文案拓展性,可自定义文案
为了可以自定义文案,我单独把文案拿了出来
export const texts = [
'我想在你那里买一块地。买什么地?买你的死心塌地',
'你知道你和星星有什么区别吗?星星在天上而你在我心里',
'我十拿九稳 就只差你一吻了',
'可爱不是长久之计,可爱我是',
'小猪佩奇,你配我',
'有谣言说我喜欢你,我澄清一下,那不是谣言',
'只许州官放火 不许……你离开我',
'你昨天晚上应该很累吧,因为你在我梦里一直跑个不停',
'我觉得你接近我就是在害我,害得我好喜欢你呀',
'你今天好奇怪,怪可爱的',
'我觉得我好花心,你每天的样子我都好喜欢',
'你有打火机嘛?没有?那你是如何点燃我的心的',
'我说不清我为什么爱你,我只知道,只要有你,我就不可能爱上别人',
'我喜欢你,像你妈打你,不讲道理',
'知道你为什么这么冷吗?因为你没有像我这么暖的对象在身边啊。',
'无事献殷勤,非……非常喜欢你',
'子曰:三思而后行,1,2,3~嗯~我喜欢你。',
'小女子不才,掐指一算,公子今生缺我。',
'你有地图吗?我在你的眼睛里迷路了。',
'你知道我最喜欢什么神吗?是你的眼神。',
'你要是丑点,我或许可以带你逛街看电影吃西餐散散步看星星看月亮,从诗词歌赋谈到人生哲学,可你长的那么好看,让我只想和你恋爱。',
' 我房租到期了,可以去你心里住吗?',
'“要是我和你生一个孩子你觉得他会是什么座?”“什么座?双子座?”“不,我们的杰作。”',
'“你可以笑一个吗?”“为什么啊?”“因为我的咖啡忘加糖了。”',
'“你想喝点什么?”“我想呵护你。”',
'“我觉得你长得像我一个亲戚。”“???”“我妈的儿媳妇。”',
'“你知道情人眼里出什么吗?”“西施啊。”“不,出现你。”',
'“你最近是不是又胖了?”“没有啊,为什么这么说?”“那你为什么在我心里的分量越来越重了呢?”',
'落叶归根,你归我。',
'苦海无涯,回头是我。',
'不想撞南墙了,只想撞撞先生胸膛。',
'你上辈子一定是碳酸饮料吧,不然我怎么一看到你就开心地冒泡呢。',
'你会弹钢琴吗?不会?那你是怎么撩动我的心弦的呢。',
'第一次见到你时,上帝在我耳旁说了几个字,在劫难逃。',
'你知道喝什么酒最容易醉吗?是你的天长地久。',
'“你属什么?”“我属虎。”“你不要再骗人了,你属于我。”',
'“你是什么星座? 双子座吗?”“ 不是。我是为你量身定做。”',
'你知道我最大的缺点是什么吗?是缺点你。',
'如果我把你推到花园里面,我就会找不到你。因为你像花儿一样美丽。',
'有时候生活有些苦难,你不要去抱怨,抱我就好了。'
];
五、业务代码
其实当我们抽离了类,抽取了非业务相关的公共方法。业务代码就非常简单了,下面就是HTML + 业务相关的代码。
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<title></title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
<!-- <script src="https://cdn.bootcss.com/jquery.qrcode/1.0/jquery.qrcode.min.js"></script> -->
</head>
<body>
<!--[if lt IE 10]>
<p class="browse-tips">
您正在使用<strong>过时的</strong>浏览器。 请<a href='http://browsehappy.com/'>更新浏览器</a>,以保证良好的用户体验。
</p>
<![endif]-->
<div class="logo">
<span>由开源H5编辑器h5ds.com提供</span>
</div>
<div id="app">
<div class="title" id="title">
<h1>
<span>七</span>
<span>夕</span>
<span>必</span>
<span>备</span>
<span>甜</span>
<span>言</span>
<span>蜜</span>
<span>语</span>
</h1>
<button id="run">生成情话</button>
</div>
<div id="phone">
</div>
<div class="qrcodebox">
<div id="qrcode">
<img src="/assets/images/qrcode.jpg" alt="">
</div>
<br>
<button id="restart">再玩一次</button>
<br>
<button id="saveImg">生成图片</button>
</div>
</div>
</body>
</html>
import * as tool from '../utils/tools';
import Text from './Text';
import domtoimage from 'dom-to-image';
// 设置尺寸
function setSize() {
const scale = tool.getScale();
tool.setSize($('#app'), scale);
}
$(function() {
setSize();
$(window).resize(() => {
setSize();
});
$('#title')
.find('span')
.each(function() {
$(this).css({
'animation-delay': `${Math.random()}s`
});
});
setTimeout(() => {
$('#title')
.find('button')
.show();
}, 2000);
const textAnim = new Text({
target: '#phone',
callback: () => {
$('.qrcodebox').show();
}
});
// 开始
$('#run').on('click', () => {
$('#title').hide();
textAnim.init();
});
// 重新选择
$('#restart').on('click', () => {
$('.qrcodebox').hide();
textAnim.restart();
});
// 保存图
$('#saveImg').on('click', () => {
domtoimage
.toPng($('body')[0])
.then(function(dataUrl) {
$('body').append(`<img id="toimg" src="${dataUrl}" />`);
alert('图片已经生成,长按屏幕保存到手机!')
})
.catch(function(error) {
console.error('oops, something went wrong!', error);
});
});
});
六、总结
在做一个项目的时候,我们尽可能的剥离出业务不相关的代码,让编程的思想尽可能的清晰,任何API接口的设计都是为了让用户更好的实现自己的业务,当然,如果对底层的实现逻辑有兴趣的朋友,也可以去具体的了解下每个方法内部的实现,进一步提升自身的实力,当然,Text这个类还可以进一步的优化,比如把数据作为参数传到类里面,这样做会更灵活!这些就是后来所谓的优化,迭代环节了。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。