【d3.js实战】中秋创意投稿,共赏明月光芒

825 阅读5分钟

共赏明月.png

当看到这个活动【🏮中秋创意投稿,共赏明月光芒 | 创意投稿大赛】的时候,我想了想什么是创意。

  • 小游戏类:月饼制作、嫦娥奔月、猜灯谜、兔子寻宝、数字拼图等;
  • 互动贺卡:可以设计角色、场景和动画效果,并通过代码控制动画的播放和交互;
  • 可爱动画:使用编程语言和动画技术,创作一个中秋节传统故事的动画短片;
  • 中秋元素:用编程语言实现各类中秋元素制作

我又想了想最近一直在更新的d3文章。好家伙,直接复制一份海报不就完事了?说干就干。

这是官方海报:

image.png

这是我用de复刻的效果:

中秋创意投稿.gif

模仿的很到位,不能说完全一样,那是基本不一样。不过我要的事是创意,那兔子,那月饼,那花花草草,d3我是写不出来的,咱就写点能写的吧。什么能写?文字能写,月亮能写,渐变能写。兔子🐰灯笼🏮月饼🥮也能写,不过是文字不是path。还要加动效。

  1. 月饼和灯笼运动起来,我感觉月饼往下运动合理一些,不然都飘走了我吃啥?
  2. 中间的主文字立体起来,给我摇摆起来。
  3. 月亮加发光效果。
  4. 嫦娥(场景唯一的图片素材。AI生成的)元素必须加上,当他靠近月亮的时候,月亮发光更大。
  5. 小兔子也跳起来。

确定了开发内容就开始敲代码。 下面是全部代码。

let width = 1000;
let height = 563;
const svg = d3.select('#svg').append('svg');

svg.attr('width', width).attr('height', height)
    .style('position', 'absolute')
    .style('top', '50%')
    .style('left', '50%')
    .style('transform', 'translate(-50%,-50%)')
    .style('background', '#262A4F')

// 4F868A 渐变色 
svg.append('defs').html(`
    <linearGradient id="linear" x1="0%" y1="0%" x2="0%" y2="100%">
        <stop offset="0%" stop-color="#4F868A" stop-opacity="0"></stop>
        <stop offset="100%" stop-color="#4F868A" stop-opacity="1"></stop>
    </linearGradient>
`)

// 月亮的光芒
let moonlight = svg.append('defs')
    .append('filter')
    .attr('id', 'moonlight')
    .append('feGaussianBlur')
    .attr('in', 'SourceGraphic')
    .attr('stdDeviation', 6)
    .attr('result', 'moonlight')

// 一个渐变的矩形 1000*563 位于底部偏上
svg.append('rect')
    .attr('x', 0)
    .attr('y', height * 0.6)
    .attr('width', width)
    .attr('height', height * 0.27)
    .attr('fill', 'url(#linear)')

//🐇
let gTu = svg.append('g')
    .attr('transform', `translate(${width},${height * 0.87})`)
let rabbit = gTu.append('text')
    .attr('font-size', 60)
    .text('🐇')
// 兔子动画 
rabbit
    .transition()
    .duration(500)
    .attr('transform', `translate(0,-30)`)
    .ease(d3.easePolyOut)
    .transition()
    .duration(400)
    .attr('transform', `translate(0,0)`)
    .ease(d3.easePolyIn)
    .on('end', function () {
        d3.select(this).transition()
            .duration(500)
            .attr('transform', `translate(0,-30)`)
            .ease(d3.easePolyOut)
            .transition()
            .duration(400)
            .attr('transform', `translate(0,0)`)
            .ease(d3.easePolyIn)
            .on('end', arguments.callee)
    })
let _t = width;
setInterval(() => {
    _t -= 0.5;
    gTu.attr('transform', `translate(${_t},${height * 0.87})`)
    if (_t < -100) {
        _t = width;
    }
}, 10);

let gTitle = svg.append('g')
// 中秋创意投稿 共赏明月光芒
let text1 = gTitle.append('text')
    .attr('x', width / 2)
    .attr('y', height * 0.32)
    .attr('text-anchor', 'middle')
    .attr('font-size', 86)
    .attr('fill', '#FBE77B')
    .attr('font-family', 'Microsoft YaHei')
    .attr('font-weight', 'bold')
    .text('中秋创意投稿')

let text2 = gTitle.append('text')
    .attr('x', width / 2 - 20)
    .attr('y', height * 0.49)
    .attr('text-anchor', 'middle')
    .attr('font-size', 86)
    .attr('fill', '#FBE77B')
    .attr('font-family', 'Microsoft YaHei')
    .attr('font-weight', 'bold')
    .text('共赏明月光芒')

//模拟立体效果
let gTitles = [];
for (let i = 0; i < 15; i++) {
    let g = gTitle.clone(true);
    g.attr('transform', `translate(${i * 1},${i * 1})`)
    g.selectAll('text')
        .attr('fill', '#50707B')
    if (i == 0) {
        g.selectAll('text')
            .attr('fill', '#FBE77B')
    }
    if (i == 14) {
        g.selectAll('text')
            .attr('filter', 'url(#moonlight)')
    }
    gTitles.push(g)
}
// 动态改变gTitle的位置 
let gTitleX = 0;
setInterval(() => {
    for (let i = 0; i < 15; i++) {
        gTitles[i].attr('transform', `translate(${i * Math.sin(gTitleX)},${i * Math.cos(gTitleX)})`)
    }
    gTitleX += 0.01;
}, 10);

let _gctitle = svg.append('g')
    .attr('transform', `translate(${width / 2},${height * 0.6 + 50})`);
let gctitle = _gctitle.append('g')

//代码舞动仪式感
let text3 = gctitle.append('text')
    .attr('text-anchor', 'middle')
    .attr('font-size', 32)
    .attr('fill', '#F6E07D')
    .attr('font-family', 'Microsoft YaHei')
    .attr('font-weight', 'bold')
    .text('代码舞动仪式感')

let text3Width = text3.node().getBBox().width;
let text3Height = text3.node().getBBox().height;
let text3X = text3.node().getBBox().x;
let text3Y = text3.node().getBBox().y;

let dy = 2

let text3Rect = gctitle.append('rect')
    .attr('x', text3X - 30)
    .attr('y', text3Y - dy)
    .attr('width', text3Width + 60)
    .attr('height', text3Height + 8)
    .attr('fill', '#F4BA5E')

let text3Triangle1 = gctitle.append('polygon')
    .attr('points', `${text3X - 30},${text3Y - dy} ${text3X - 30},${text3Y - dy + text3Height + 8} ${text3X - 30 - 30},${text3Y - dy + text3Height / 2 + 4}`)
    .attr('fill', '#F4BA5E')

let text3Triangle2 = gctitle.append('polygon')
    .attr('points', `${text3X + text3Width + 30},${text3Y - dy} ${text3X + text3Width + 30},${text3Y - dy + text3Height + 8} ${text3X + text3Width + 30 + 30},${text3Y - dy + text3Height / 2 + 4}`)
    .attr('fill', '#F4BA5E')

let text4 = gctitle.append('text')
    .attr('text-anchor', 'middle')
    .attr('font-size', 32)
    .attr('fill', '#252A48')
    .attr('font-family', 'Microsoft YaHei')
    .attr('font-weight', 'bold')
    .text('代码舞动仪式感')


//活动时间:2023年9月 12日-2023年9月24日
let text5 = svg.append('text')
    .attr('x', width / 2)
    .attr('y', height * 0.6 + 50 + 60)
    .attr('text-anchor', 'middle')
    .attr('font-size', 22)
    .attr('fill', '#FFFFFF')
    .attr('font-family', 'Microsoft YaHei')
    .attr('font-weight', 'bold')
    .text('活动时间:2023年9月12日-2023年9月24日')

// 左上角 
let gltitle = svg.append('g')
    .attr('transform', `translate(${width * 0.08},${height * 0.12})`)
let ltitle = gltitle.append('text')
    .attr('x', -6)
    .attr('y', 0)
    .attr('font-size', 28)
    .attr('fill', '#FFFFFF')
    .attr('font-family', 'Microsoft YaHei')
    .attr('dominant-baseline', 'middle')
    .text('稀士掘金')
gltitle.append('rect')
    .attr('width', 4)
    .attr('height', 4)
    .attr('fill', '#FFFFFF')
    .attr('transform', `translate(-28,-15) rotate(45) `)
gltitle.append('path')
    .attr('d', 'M-8,0 L0,8 L8,0')
    .attr('fill', 'none')
    .attr('stroke', '#FFFFFF')
    .attr('stroke-width', 3)
    .attr('transform', `translate(-28,-10)`)

gltitle.append('path')
    .attr('d', 'M-12,0 L0,12 L12,0')
    .attr('fill', 'none')
    .attr('stroke', '#FFFFFF')
    .attr('stroke-width', 3)
    .attr('transform', `translate(-28,-5)`)

//Data_Adventure 
gltitle.append('text')
    .attr('x', -6)
    .attr('y', 30)
    .attr('font-size', 16)
    .attr('fill', '#FFFFFF80')
    .attr('font-family', 'Microsoft YaHei')
    .attr('dominant-baseline', 'middle')
    .text('Data_Adventure')


// 月亮
let moonRadius = 65;
let moonX = width - moonRadius * 2.55;
let moonY = moonRadius * 0.6

let moon = svg.append('circle')
    .attr('cx', moonX)
    .attr('cy', moonY)
    .attr('r', moonRadius)
    .attr('fill', '#F6E07D')
    .attr('filter', 'url(#moonlight)')
    .attr('class', 'moon');
let moon1 = svg.append('circle')
    .attr('cx', moonX)
    .attr('cy', moonY)
    .attr('r', moonRadius)
    .attr('fill', '#F6E07D')
    .attr('filter', 'url(#moonlight)')
    .attr('class', 'moon')
    .attr('opacity', 0)

let moon2 = svg.append('circle')
    .attr('cx', moonX)
    .attr('cy', moonY)
    .attr('r', moonRadius)
    .attr('fill', '#F6E07D')
    .attr('class', 'moon')

// 🏮灯笼 text
let gdeng = svg.append('g')
// 随机生成灯笼
let dengs = [];
for (let i = 0; i < 88; i++) {
    let x = Math.random() * width;
    let y = Math.random() * height;
    let _k = Math.random();
    let speed = Math.random() * 0.5 + 0.5;
    let deng = gdeng.append('text')
        .attr('font-size', 28)
        .attr('fill', '#FF780C')
        .attr('font-family', 'Microsoft YaHei')
        .attr('dominant-baseline', 'middle')
        .text(() => _k > 0.5 ? '🏮' : '🥮')
        .attr('opacity', 0)
        .attr('transform', `translate(${x},${y})`)
    dengs.push({
        x, y, speed, deng
    })
}

setInterval(() => {
    for (let i = 0; i < dengs.length; i++) {
        let deng = dengs[i];
        if (deng.y < -100) {
            deng.x = Math.random() * width;
            deng.y = height + 100;
            deng.speed = Math.random() * 0.5 + 0.5;
        }
        deng.y -= deng.speed;
        deng.deng.attr('transform', `translate(${deng.x},${deng.y})`)
        deng.deng.attr('opacity', 1)
    }
}, 10);

// 嫦娥出场了
let defs = svg.append('defs');
let pattern = defs.append('pattern')
    .attr('id', 'bg')
    .attr('x', '0%')
    .attr('y', '0%')
    .attr('height', '100%')
    .attr('width', '100%')
    .attr('viewBox', `0 0 ${100} ${100}`)
    .append('image')
    .attr('x', '0%')
    .attr('y', '0%')
    .attr('height', 100)
    .attr('width', 100)
    .attr('xlink:href', './e.png')

let Change = svg.append('circle')
    .attr('cx', width/2)
    .attr('cy', height/2)
    .attr('r', moonRadius)
    .attr('fill', 'url(#bg)')
    .attr('class', 'moon')

// 鼠标事件
svg.on('mousemove', function () {
    let x = d3.event.offsetX;
    let y = d3.event.offsetY;
    Change.attr('cx', x).attr('opacity', 1)
        .attr('cy', y)
        if (Math.abs(x - moonX) < moonRadius * 2 && Math.abs(y - moonY) < moonRadius * 2) {
            moon1.attr('opacity', 1)
        } else {
            moon1.attr('opacity', 0)
        }
})
// 鼠标移出
svg.on('mouseout', function () {
    Change.attr('opacity', 0)
})

补充一张素材图,码上用,希望能用。

e.png

在线效果: