みかづきブログ その3

本ブログは更新を終了しました。通算140万ユーザーの方に観覧頂くことができました。長い間、ありがとうございました。

👆

引越し先はこちらです!

Canvasに紙吹雪を舞い散らせてみました。

DEMO

See the Pen Confetti by kimmy (@kimmy) on CodePen.


JavaScript

class Progress {
  constructor(param = {}) {
    this.timestamp        = null;
    this.duration         = param.duration || Progress.CONST.DURATION;
    this.progress         = 0;
    this.delta            = 0;
    this.progress         = 0;
    this.isLoop           = !!param.isLoop;

    this.reset();
  }

  static get CONST() {
    return {
      DURATION : 1000
    };
  }

  reset() {
    this.timestamp = null;
  }

  start(now) {
    this.timestamp = now;
  }

  tick(now) {
    if (this.timestamp) {
      this.delta    = now - this.timestamp;
      this.progress = Math.min(this.delta / this.duration, 1);

      if (this.progress >= 1 && this.isLoop) {
        this.start(now);
      }

      return this.progress;
    } else {
      return 0;
    }
  }
}

class Confetti {
  constructor(param) {
    this.parent         = param.elm || document.body;
    this.canvas         = document.createElement("canvas");
    this.ctx            = this.canvas.getContext("2d");
    this.width          = param.width  || this.parent.offsetWidth;
    this.height         = param.height || this.parent.offsetHeight;
    this.length         = param.length || Confetti.CONST.PAPER_LENGTH;
    this.yRange         = param.yRange || this.height * 2;
    this.progress       = new Progress({
      duration : param.duration,
      isLoop   : true
    });
    this.rotationRange  = typeof param.rotationLength === "number" ? param.rotationRange
                                                                   : 10;
    this.speedRange     = typeof param.speedRange     === "number" ? param.speedRange
                                                                   : 10;
    this.sprites        = [];

    this.canvas.style.cssText = [
      "display: block",
      "position: absolute",
      "top: 0",
      "left: 0",
      "pointer-events: none"
    ].join(";");

    this.render = this.render.bind(this);

    this.build();

    this.parent.appendChild(this.canvas);
    this.progress.start(performance.now());

    requestAnimationFrame(this.render);
  }

  static get CONST() {
    return {
        SPRITE_WIDTH  : 9,
        SPRITE_HEIGHT : 16,
        PAPER_LENGTH  : 100,
        DURATION      : 8000,
        ROTATION_RATE : 50,
        COLORS        : [
          "#EF5350",
          "#EC407A",
          "#AB47BC",
          "#7E57C2",
          "#5C6BC0",
          "#42A5F5",
          "#29B6F6",
          "#26C6DA",
          "#26A69A",
          "#66BB6A",
          "#9CCC65",
          "#D4E157",
          "#FFEE58",
          "#FFCA28",
          "#FFA726",
          "#FF7043",
          "#8D6E63",
          "#BDBDBD",
          "#78909C"
        ]
    };
  }

  build() {
    for (let i = 0; i < this.length; ++i) {
      let canvas = document.createElement("canvas"),
          ctx    = canvas.getContext("2d");

      canvas.width  = Confetti.CONST.SPRITE_WIDTH;
      canvas.height = Confetti.CONST.SPRITE_HEIGHT;

      canvas.position = {
        initX : Math.random() * this.width,
        initY : -canvas.height - Math.random() * this.yRange
      };

      canvas.rotation = (this.rotationRange / 2) - Math.random() * this.rotationRange;
      canvas.speed    = (this.speedRange / 2) + Math.random() * (this.speedRange / 2);

      ctx.save();
        ctx.fillStyle = Confetti.CONST.COLORS[(Math.random() * Confetti.CONST.COLORS.length) | 0];
        ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.restore();

      this.sprites.push(canvas);
    }
  }

  render(now) {
    let progress = this.progress.tick(now);

    this.canvas.width  = this.width;
    this.canvas.height = this.height;

    for (let i = 0; i < this.length; ++i) {
      this.ctx.save();
        this.ctx.translate(
          this.sprites[i].position.initX + this.sprites[i].rotation * Confetti.CONST.ROTATION_RATE * progress,
          this.sprites[i].position.initY + progress * (this.height + this.yRange)
        );
        this.ctx.rotate(this.sprites[i].rotation);
        this.ctx.drawImage(
          this.sprites[i],
          -Confetti.CONST.SPRITE_WIDTH * Math.abs(Math.sin(progress * Math.PI * 2 * this.sprites[i].speed)) / 2,
          -Confetti.CONST.SPRITE_HEIGHT / 2,
          Confetti.CONST.SPRITE_WIDTH * Math.abs(Math.sin(progress * Math.PI * 2 * this.sprites[i].speed)),
          Confetti.CONST.SPRITE_HEIGHT
        );
      this.ctx.restore();
    }

    requestAnimationFrame(this.render);
  }
}

(() => {
  const DURATION = 8000,
        LENGTH   = 120;

  new Confetti({
    width    : window.innerWidth,
    height   : window.innerHeight,
    length   : LENGTH,
    duration : DURATION
  });

  setTimeout(() => {
    new Confetti({
      width    : window.innerWidth,
      height   : window.innerHeight,
      length   : LENGTH,
      duration : DURATION
    });
  }, DURATION / 2);
})();

これまでつくってきたものはこちらにまとまってます!

twitter.com

夏なのでCSSで蚊取り線香をつくって蚊が寄ってこないページをつくりました。

See the Pen mosquito coil with mosquito sound by kimmy (@kimmy) on CodePen.

まず、CSSで蚊取り線香をつくりました。

半円を互い違いに組み合わせて実装しております。

また、クリックすると火が着くのですが、

f:id:kimizuka:20170811004025p:plain

その際、蚊の嫌がる周波数の音を出しています。

f:id:kimizuka:20170811004036p:plain

なので、原理的にはサイト上の蚊取り線香に火をつければ蚊が寄ってこないわけです。
バナーや夏フェスのサイトとかに組み込むと、とても良いと思ってます。

Pug

#mosquito-coil
  - var length = 6;
  - for (var i = 0; i < length; ++i) {
    .circle
  - }

SCSS

$length: 6;
$width: 10px;
$color: #004D40;

body {
  background: #212121;
}

#mosquito-coil {
  position: absolute;
  top: 50%; left: 50%;
  cursor: pointer;
  
  &:before {
    display: block;
    position: absolute;
    top: $width;
    @if ($length % 2 == 0) {
      left: $width * -2 * ceil($length / 2);
    } @else {
      left: $width * (1 + $length);
    }
    border-radius: 50%;
    width: $width; height: $width;
    background: $color;
    content: "";
    transform: scale(.9);
    transition: background 12s ease-in-out;
  }

  &:after {
    display: block;
    position: absolute;
    top: $width; left: 0;
    border-radius: 50%;
    width: $width; height: $width;
    background: $color;
    content: "";
  }

  .circle {
    position: absolute;
    border: solid $width $color;

    &:nth-child(odd) {
      border-radius: 50% 50% 0 0 / 100% 100% 0 0;
      border-bottom: none;
    }

    &:nth-child(even) {
      border-radius: 0 0 50% 50% / 0 0 100% 100%;
      border-top: none;
    }

    @for $i from 0 through $length {
      &:nth-child(#{$i + 1}) {
        @if $i % 2 == 1 {
          top: $width * 1.5;
        } @else {
          top: -$width * $i;
        }
        left: $width * -2 * ceil($i / 2);
        width: $width * (1 + $i * 2);
        height: $width * (0.5 + 1 * $i); 
      }
    }
  }
  
  &.on {
    &:before {
      background: #4d0000;
      animation: mosquito-coil-blink 1.8s ease-in-out infinite alternate;
    }
  }
}

@keyframes mosquito-coil-blink {
  0% {
    box-shadow: 0 0 0 rgba(255, 255, 255, .8);
  }
  100% {
    box-shadow: 0 0 5px rgba(255, 255, 255, .8);
  }
}

JavaScript

(() => {
  
  "use strict";
  
  let AudioContext = window.AudioContext || window.webkitAudioContext,
      ctx = new AudioContext(),
      oscillator = ctx.createOscillator(),
      coil = document.getElementById("mosquito-coil"),
      isOn = false;
  
  coil.addEventListener("click", () => {
    isOn = !isOn;
    if (isOn) {
      oscillator = ctx.createOscillator();
      oscillator.frequency.value = 17000;
      oscillator.connect(ctx.destination);
      coil.classList.add("on");
      oscillator.start();
    } else {
      oscillator.stop();
      coil.classList.remove("on");
    }

  }, false);
  
})();

レギュラー番組ゼロでもCM出演があれば暮らしていけるのだろうか?

f:id:kimizuka:20170718181708p:plain

こどもが生まれてからは、朝、一緒にEテレを観てから出社してます。

先日、いつものようにEテレを観ていたら、「コニちゃん」というキャラクターがでてまして、

「そういえば、ポンキッキーズに『コニーちゃん』ってキャラクターでてたなー。」


と懐かしい気持ちになりました。

懐かしい気持ちのまま、

「Eテレを贔屓にしてたけど、たまにはポンキッキーズを観ようかなー。」

と妻に話してみると、

「え!?ポンキッキーズなんてとっくの昔に終わった(※1)よ!」

と言われました。


え!?


まじかよ!



ガチャピン、ムック、いま無職なのかよ!



そりゃー。UQモバイルのCMにもでるわけだ。


「宇宙人家族」篇

と思いました。

(※1)調べてみると、2017年4月から、BSフジで「ポンキッキーズ」が再開したようです。これでCMを降板になっても安心ですね。^ ^





ツイッターで #台無しプロダクト を発表してます!
こちらもよろしくお願いします。