Skip to content

Instantly share code, notes, and snippets.

@SevenChan07
Last active December 7, 2017 13:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SevenChan07/8e4a380470c7c89b519d8ea353e2d372 to your computer and use it in GitHub Desktop.
Save SevenChan07/8e4a380470c7c89b519d8ea353e2d372 to your computer and use it in GitHub Desktop.
Particle Text
license: gpl-3.0
height: 700
<!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>
// 颗粒文字
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