Particle Text.
Last active
December 7, 2017 13:01
-
-
Save SevenChan07/8e4a380470c7c89b519d8ea353e2d372 to your computer and use it in GitHub Desktop.
Particle Text
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 | |
height: 700 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
background: #111 | |
} | |
</style> | |
<body> | |
<script src="//d3js.org/d3.v4.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js"></script> | |
<script src="particleText.js"></script> | |
<div id="action"> | |
<button id="retry">retry</button> | |
<input id="input" placeholder="please input" /> | |
</div> | |
<script> | |
var width = 960, | |
height = 500, | |
radius = 3, | |
collisionStrength = 0.1 | |
var options = { | |
width: width, | |
height: height, | |
spacing: 10, | |
fontSize: "250px" | |
} | |
var svg = d3.select("body").insert("svg", '#action') | |
.attr("width", width) | |
.attr("height", height) | |
var p = new ParticleText({ | |
text: 'DTWave', | |
...options | |
}) | |
// 再来一次 | |
d3.select('#retry').on('click', () => { | |
d3.select('.circles').remove() | |
p.drawText() | |
}) | |
// 输入框监听输入事件 | |
$('#input').bind('keydown',function(event, t){ | |
if(event.keyCode == "13") { | |
var value = event.target.value.trim() | |
if (value !== '') { | |
p.loadText(value) | |
} | |
} | |
}) | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 颗粒文字 | |
class ParticleText { | |
constructor(opts) { | |
this.text = opts.text | |
this.opts = opts | |
// 文字的像素位置 | |
this.pixels = this.rasterizeText(opts) | |
.map((d,e,t) => { | |
var x = Math.random() * width | |
return { | |
x: x, | |
y: Math.random() * height, | |
xTarget: d[0], | |
yTarget: d[1], | |
rTarget: radius + Math.abs(width/2 - x)/width*2 * 2 | |
} | |
}) | |
// 画文字 | |
this.drawText() | |
} | |
drawText() { | |
var maxR = d3.max(this.pixels, function(d) { return d.rTarget }) | |
var color = d3.scaleSequential(d3.interpolateRainbow) | |
.domain([0, 960]) | |
var mouseNode = { | |
x: 0, | |
y: height / 2, | |
xTarget: width + 100, | |
yTarget: height / 2, | |
rTarget: 50 | |
} | |
// 深拷贝,为了重绘 | |
var copyPixels = _.cloneDeep(this.pixels) | |
var pText = svg.append("g") | |
.attr("class", "circles") | |
.selectAll("circle") | |
.data(copyPixels) | |
.enter() | |
.append("rect") | |
.classed("mouse", function(d, i) { return i == 0 }) | |
.attr("x", function(d) { return d.x }) | |
.attr("y", function(d) { return d.y }) | |
.attr("width", function(d) { return d.rTarget * 1.5 }) | |
.attr("height", function(d) { return d.rTarget * 1.5 }) | |
// velocityDecay,速度衰减系数,相当于摩擦力。区间[0,1],默认0.4。速度衰减过慢,会导致震荡。 | |
var simulation = d3.forceSimulation([mouseNode].concat(copyPixels)) | |
.velocityDecay(0.2) | |
.force("x", d3.forceX(function(d) { return d.xTarget }).strength(collisionStrength)) | |
.force("y", d3.forceY(function(d) { return d.yTarget }).strength(collisionStrength)) | |
.force("collide", d3.forceCollide().radius(function(d) { return d.rTarget })) | |
.on("tick", ticked) | |
// tick事件 | |
// 更新粒子的位置 | |
function ticked() { | |
pText | |
.attr("x", function(d) { return d.x }) | |
.attr("y", function(d) { return d.y }) | |
.style("fill", function(d){ | |
return color(d.x) | |
}) | |
} | |
// 鼠标移动事件 | |
d3.select("body") | |
.on("mousemove", mousemove) | |
function mousemove() { | |
var p = d3.mouse(this) | |
mouseNode.xTarget = p[0] | |
mouseNode.yTarget = p[1] | |
simulation | |
.force("x", d3.forceX(function(d) { return d.xTarget }).strength(collisionStrength)) | |
.force("y", d3.forceY(function(d) { return d.yTarget }).strength(collisionStrength)) | |
.alpha(1) | |
.restart() | |
} | |
} | |
// 像素级栅格文字 | |
rasterizeText(options) { | |
var o = options || {} | |
var fontSize = o.fontSize || "200px", | |
fontWeight = o.fontWeight || "600", | |
fontFamily = o.fontFamily || "sans-serif", | |
textAlign = o.center || "center", | |
textBaseline = o.textBaseline || "middle", | |
spacing = o.spacing || 10, | |
width = o.width || 960, | |
height = o.height || 500, | |
x = o.x || (width / 2), | |
y = o.y || (height / 2) | |
// 创建一个canvas | |
var canvas = document.createElement("canvas") | |
canvas.width = width | |
canvas.height = height | |
var context = canvas.getContext("2d") | |
context.font = [fontWeight, fontSize, fontFamily].join(" ") | |
context.textAlign = textAlign | |
context.textBaseline = textBaseline | |
// 文字的长,宽,以及在画布中的位置 | |
var dx = context.measureText(this.text).width, | |
dy = +fontSize.replace("px", ""), | |
bBox = [[x - dx / 2, y - dy / 2], [x + dx / 2, y + dy / 2]] | |
// 在canvas画布中写字 | |
context.fillText(this.text, x, y) | |
// 获取该画布 | |
var imageData = context.getImageData(0, 0, width, height) | |
var pixels = [] | |
for (var x = bBox[0][0]; x < bBox[1][0]; x += spacing) { | |
for (var y = bBox[0][1]; y < bBox[1][1]; y += spacing) { | |
// 记录有颜色的位置 | |
var pixel = this.getPixel(imageData, x, y) | |
if (pixel[3] != 0) pixels.push([x, y]) | |
} | |
} | |
return pixels | |
} | |
getPixel(imageData, x, y) { | |
var i = 4 * (parseInt(x) + parseInt(y) * imageData.width) | |
var d = imageData.data | |
return [ d[i], d[i+1], d[i+2], d[i+3] ] | |
} | |
// 绘制传入的文字 | |
loadText(text) { | |
d3.select('.circles').remove() | |
this.text = text | |
this.pixels = this.rasterizeText(this.opts) | |
.map(d => { | |
var x = Math.random() * width | |
return { | |
x: x, | |
y: Math.random() * height, | |
xTarget: d[0], | |
yTarget: d[1], | |
rTarget: radius + Math.abs(width/2 - x)/width*2 * 2 | |
} | |
}) | |
this.drawText() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment