くりーむわーかー

プログラムとか。作ってて ・試しててハマった事など。誰かのお役に立てば幸いかと。 その他、いろいろエトセトラ。。。

javascript

Webシステムのスクリーンショットをjavascriptとか使ってとる

Webの業務システムなんかを作ってるとシステムのスクリーンショットを撮りたいっていう要望がだいたいあがる。PrintScreenでがんばってで終わればいいんだけど、システムの中でやってとか、まーお客さんの環境によってはキャプチャが動かせなかったり色々あるので、システムでやらないとまずい場合がある。あと、スクロールするページの場合、一回のスクショで全体を撮りたいみたいな要望もあったりする。

そん時にどうやるかという事。VueとかNuxtで作ったSPAなアプリとかでやりたい場合とかも。

最近のやり方としては↓の感じなのかなーと思う。

  1. ブラウザのネイティブの機能を使う
  2. javascriptのライブラリを使う
  3. サーバ側で処理する

ブラウザのネイティブの機能を使う

これはChrome前提。自分は使ったことないんだけど、↓あたりのものを使えばいけそう。

Chrome拡張を使うか自分で作ってやる感じのやり方。

javascriptのライブラリを使う

javascriptでやる場合はだいたい下記を使うんじゃないだろうか。

htmlをcanvasにレンダリングするのをjavascriptで作った変態(褒め言葉)モジュール。スターの数とかえらいことになってるの。相当使えるんだけど、100%の再現率じゃない。対応してないstyleの属性とかもあって、ブラウザで見えてるものとはちょっと変わっちゃう。スクショの用途にもよるけど、ある程度の再現が出来てない場合は使えない。あと、ここ半年くらい更新されて無いのでちょっと心配。

でも、特定のDOMだけレンダリングしないとか出来て、例えば個人情報的にこの場所は非表示にしてスクショ撮りたいとかの要望にも対応出来たりする。

サーバ側で処理する

で、自分はこれがいいと思う。サーバにブラウザで表示されてるHTMLのDOMを丸ごと送って、サーバ側で、サーバのchromeなんかで、レンダリングしてスクショ撮る感じ。

今見えてる状態のHTMLを送りつけるので、VueとかNuxtとかのSPAでもちゃんと上手くいく。

で、ブラウザのエミュレータみたいなものが必要なんだけど、最近だとpuppeteer っていうのがすごく使える。Googleが作ってるOSSのものらしいですよ。

スクショ撮った後に、パスワードかけて圧縮してDLさせたりとか色々制御できるのでこのやり方が一番融通効くと思う。

で、やり方。

クライアント側
//現在のHTMLを丸ごと取得
const htmldoc = document.documentElement.cloneNode(true)
//スクリプトタグが邪魔なので全部消す
;[...htmldoc.querySelectorAll('script')].forEach(e => e.remove())
//HTML文字列を取得
const htmlstr = htmldoc.outerHTML
//サーバに送信
this.$axios
.post('http://hoge.fuga/api/save', { data: htmlstr })
.then(function() {
	console.log('OK')
})

サーバにHTMLの文字列丸ごと送ってサーバ側のローカルに保存。そしたらパペティア動かす。

ローカルのファイルを見る場合は↓の感じでやる。

サーバ側

"use strict";
const puppeteer = require("puppeteer");

(async () => {
  //centosで動かす場合は--no-sandboxのオプションをつけないと動かない
  const browser = await puppeteer.launch({
    args: ["--no-sandbox", "--disable-setuid-sandbox"]
  });
  //ブラウザの定義
  const page = await browser.newPage();
  //画面の大きさ指定
  page.setViewport({ width: 1600, height: 900 });
  //保存してあるファイルを読み込み
  await page.goto("file:///home/hogeuser/saved.html", {
    waitUntil: "networkidle0" //遅延ロード
  });
  //スクリーンショットをページ全体で取る場合
  await page.screenshot({ path: "example.png", fullPage: true });
  //PDFをA4で作る場合
  await page.pdf({ path: "test.pdf", format: "A4" });
  //ブラウザを閉じる
  await browser.close();
})();

パペティアの方で色々制御したりも出来るのであとは要件しだい。

ついでに、Webフォントとか使ってる場合、font-faceの指定のところとかサーバ側のローカルにしておけばスクショ側でもちゃんとWebフォントが反映される。

パペティア自体はe2eのテストに使ったり、色んなサイトを巡回したりする用途が普通なのかしらね。

ただ、ブラウザの一番外のスクロールなら「fullPage: true」付ければ全画面のスクショ撮ってくれんるんけど、中のDOMのスクロールはさすがに無理なので、必要ならその辺を展開してからサーバに送るとかが必要かな。

D3のline()

D3を久しぶりに触ったらしょーもない事でちょっとはまった。

D3はv4から「d3.svg.line()」がなくなったのね。。。

v4からは「d3.line()」を使えとゆーことらしい。

「d3.svg.line()」を書くと、chromeだと↓のエラーが出る。

The error is: Uncaught TypeError: Cannot read property 'line' of undefined

なわけねーと思って調べてみたらv4からは「d3.line()」って書いてあった。ぐぐって出てくる日本語のサンプルソースが軒並み古い。

参考にしたのはココ

D3使ってワードクラウドを作る

以前(とか)、Rでワードクラウドを作るのをやったけど、やっぱ固定の画像じゃなくしたい。とゆーわけで、D3使ってSVGで作れるようにしたい。テキストクリックで何かにリンクとかさ。出来たらいいなー。

とりえあず、ランダム配置で作ってみる。↓の感じ。

tamesi

うーん、イマイチ。イマサンくらい。とりえあず、文字が重ならないようにはすぐできた。 getBBox()っていうのが組み込みにあるのね。初めてしった。まぁ、こーゆーことやろうとしないと 用途がなさそうだしの。。。

フォントサイズとか色の段階のつけ方を工夫すればもうちょっとよくなるかしら。 配置の仕方も調べてみたけど割とめんどーな気がする。とりえあず、サイズと色の工夫をしてみましょ。

javascript D3使って集計とか

D3使ってオブジェクト内の集計をやりたくなった。ちょっとだけ複雑なオブジェクトでやりたい。 例えば下くらいのオブジェクトの配列で。

var hogeobj= {
    date: "2016-04-01",
    group: {
        classname: "都道府県",
        values: [
            { name1: "青森", name2: "りんご", value: 6 },
            { name1: "秋田", name2: "美人", value: 10 },
            { name1: "秋田", name2: "きりたんぽ", value: 2 },
            { name1: "宮城", name2: "笹かま", value: 3 },
            { name1: "青森", name2: "りんご", value: 8 },
            { name1: "青森", name2: "美人", value: 4 },
            { name1: "秋田", name2: "笹かま", value: 15 },
            { name1: "宮城", name2: "桜", value: 1 },
        ]
    }
}

上の感じのデータセットで、例えば、各オブジェクト内のname1のグループ毎にvalueを合計したいとか。下の感じ。

var groupCountObj = d3.nest()
                            .key(function (d) { return d.name1; })
                            .rollup(function (v) { return d3.sum(v, function (d) { return +d.value; }); })
                            .map(hogeobj.group.values);
console.log(groupCountObj);
->Object {青森: 18, 秋田: 27, 宮城: 4}

集計はされるんだけど、Objectで結果が戻ってくるので割と扱いにくい。なので、配列にkey-value的な感じでmapする。

var tmpob = $.map(groupCountObj, function (value, index) {
    return { label: index, value: value }
});
console.log(tmpob);
->[Object, Object, Object]
0:Object
label:"青森"
value:18
__proto__:Object

1:Object
label:"秋田"
value:27
__proto__:Object

2:Object
label:"宮城"
value:4
__proto__:Object

length:3
__proto__:Array[0]

これでD3使っての円グラフとかに使いやすくなる。

久しぶりにD3使ったけど、すごい忘れてる。。。えらいこっちゃ。

javascriptでホイール使って入力する

時間とか日付とかホイールで入力できると割とストレスフリー。なので、jqueryとmousewheel.jsを使って実装してみる。

HTML側

<input type="text" id="hoge" name="hoge" value="0" />

javascript側

時間を入力。

$('input[name=hoge]').mousewheel(function(event, delta, deltaX, deltaY){
    $targetElm = $(this)
    var currentVal = parseInt(targetElm.val());//現在値
    var nextVal = 0;
    nextVal = currentVal + deltaY;
    if(nextVal < 0) nextVal = 23;
    nextVal = nextVal % 24;
    elm.val(nextVal);
    
    //ホイールイベントをここで止める(スクロールを止める)
    if (event.preventDefault) {
        event.preventDefault();
    }
    event.returnValue = false;//これないと効かないブラウザもあった気がする
});

日付を入力。日付の加減算はmoment.jsを使用。エラーチェックなし。

$('input[name=hoge]').mousewheel(function(event, delta, deltaX, deltaY){
    $targetElm = $(this)
    var m = moment($targetElm.val(), "YYYY-MM-DD");
    if(deltaY > 0)
        m.add(1,"days")
    else
        m.subtract(1,"days")
    $targetElm.val(m.format("YYYY-MM-DD"));
    
    //ホイールイベントをここで止める(スクロールを止める)
    if (event.preventDefault) {
        event.preventDefault();
    }
    event.returnValue = false;//これないと効かないブラウザもあった気がする
});


javascriptで画面の全体スクロールさせないようにする

Webで入力する時、例えばテキストエリアとかみたいなので、ホイールクリクリしてエリア内のスクロールだけ動かしたいんだけど、一番上とか下まで行くと、そのまま全体スクロールされる。

個人的にこの動きがホント嫌い。いらっとする。なので、特定のDOM内でスクロールしてる時は全体スクロールさせないためのスクリプト。jqueryとmousewheel.jsを使用。DOM内のスクロールは自分でやらないとダメ。

$('textarea[name=test]').mousewheel(function(event, delta, deltaX, deltaY){
    scrLen = 40
    $targetElm = $(this)
    if (deltaY < 0){
        $targetElm.scrollTop($targetElm.scrollTop()+scrLen);
    } else {
        $targetElm.scrollTop($targetElm.scrollTop()-scrLen);
    }
    //ホイールイベントをここで止める(スクロールを止める)
    if (event.preventDefault) {
        event.preventDefault();
    }
    event.returnValue = false;//これないと効かないブラウザもあった気がする
});

mousewheel.jsが便利。とても便利。

ちなみに、mousewheel.jsが無い版はこんな感じ。

var mousewheelevent = 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll';
$("#hoge").on(mousewheelevent,function(e){
    scrLen = 30;
    var delta = e.originalEvent.deltaY ? -(e.originalEvent.deltaY) : e.originalEvent.wheelDelta ? e.originalEvent.wheelDelta : -(e.originalEvent.detail);
    var $targetElm = $(this);
    if (delta < 0){
        $targetElm.scrollTop($targetElm.scrollTop()+scrLen);
    } else {
        $targetElm.scrollTop($targetElm.scrollTop()-scrLen);
    }
    //ホイールイベントをここで止める(スクロールを止める)
    if (e.preventDefault) {
        e.preventDefault();
    }
    e.returnValue = false;//これないと効かないブラウザもあった気がする
});

d3でSVG

d3使ってSVGを追加する時のメモ。

・ 一括で追加する

下の感じ。
var dataset = [10,5,15];
var svg = d3.select("body")
	.append("svg")	
	.attr("width",500)
	.attr("height",500);

svg.selectAll("rect")
	.data(dataset)
	.enter()
	.append("rect")
	.attr("x",5)
	.attr("y",function(d,i){return i*42;})
	.attr("width",50)
	.attr("height",function(d,i){return 40;});

・ 一括で追加のを2回に分けてやる

下の感じ。
var dataset = [10,5,15];
var svg = d3.select("body")
	.append("svg")	
	.attr("width",500)
	.attr("height",500);

svg.selectAll("rect")
	.data(dataset)
	.enter()
	.append("rect")
	.attr("x",5)
	.attr("y",function(d,i){return i*42;})
	.attr("width",50)
	.attr("height",function(d,i){return 40;});

svg.selectAll("rect2")
	.data(dataset)
	.enter()
	.append("rect")
	.attr("x",5)
	.attr("y",function(d,i){return i*42;})
	.attr("width",50)
	.attr("height",function(d,i){return 40;});

よく分からないけど、足したい時はselectAllで持ってくる要素を空振りさせるようにしないと上手くいかない。

flotr2のアレコレ

flotr2を使ってみてのアレコレ。 簡単なサンプルは大本のサンプル見れば良いです。 何個かやりたいのが載ってなかったので試してみた。

・2軸を設定する

下の感じで、Y軸を二つにしたい場合。
flotr2_1
options = {
	xaxis : {
		mode : 'time', 
		timeFormat : "%y/%m",
		labelsAngle : 45
	},
	selection : {
		mode : 'x'
	},
	y2axis:{color:'#0000FF', max: 100, },
	HtmlText : false,
	title : 'さむしんぐえるすの推移'
};
Flotr.draw(
	container,
	[	{data :graData , label:'HOGE',lines:{show:true},points : {show : true} },
		{data :graData2 , label:'FUGA', lines:{show:true},points : {show : true},yaxis:2},
		{data :graData3 , label:'MOE', lines:{show:true},points : {show : true},yaxis:2}
	],
	o
);
y2axisをオプションで指定して、グラフを乗せる時にyaxis:2をつける感じ。

・年月日の表示フォーマットを指定する

上のグラフみたいにX軸の年月日表示のフォーマットを指定する場合
options = {
	xaxis : {
		mode : 'time', 
		timeFormat : "%y/%m",
		labelsAngle : 45
	},
	selection : {
		mode : 'x'
	},
	y2axis:{color:'#0000FF', max: 100, },
	HtmlText : false,
	title : 'さむしんぐえるすの推移'
};
timeFormat で "%y/%m"みたいに指定する。
ただし、拡大すると↓みたいに、フォーマットが強制される。 flotr2_2
上の英語表記をどうにかできないものか。。。
ソースを直接変えるしかないのかしら。。。
問合せ